diff --git a/TUnit.Core.SourceGenerator/Generators/AotMethodInvocationGenerator.cs b/TUnit.Core.SourceGenerator/Generators/AotMethodInvocationGenerator.cs deleted file mode 100644 index 76caa1ace6..0000000000 --- a/TUnit.Core.SourceGenerator/Generators/AotMethodInvocationGenerator.cs +++ /dev/null @@ -1,717 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using TUnit.Core.SourceGenerator.CodeGenerators; -using TUnit.Core.SourceGenerator.Extensions; - -namespace TUnit.Core.SourceGenerator.Generators; - -/// -/// Generates AOT-compatible method invocation code to replace MethodInfo.Invoke calls -/// -[Generator] -public sealed class AotMethodInvocationGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // Find all method data source attributes and methods that need invocation - var methodDataSources = context.SyntaxProvider - .CreateSyntaxProvider( - predicate: (node, _) => IsMethodDataSourceUsage(node), - transform: (ctx, _) => ExtractMethodDataSourceInfo(ctx)) - .Where(x => x is not null) - .Select((x, _) => x!); - - // Find all MethodInfo.Invoke usage - var methodInvocations = context.SyntaxProvider - .CreateSyntaxProvider( - predicate: (node, _) => IsMethodInfoInvocation(node), - transform: (ctx, _) => ExtractMethodInvocationInfo(ctx)) - .Where(x => x is not null) - .Select((x, _) => x!); - - // Combine all method invocation requirements - var allMethodInfo = methodDataSources - .Collect() - .Combine(methodInvocations.Collect()); - - // Generate the method invocation helpers - context.RegisterSourceOutput(allMethodInfo, GenerateMethodInvokers); - } - - private static bool IsMethodDataSourceUsage(SyntaxNode node) - { - // Look for MethodDataSourceAttribute usage - if (node is AttributeSyntax attribute) - { - var name = attribute.Name.ToString(); - return name.Contains("MethodDataSource") || name.Contains("MethodDataSourceAttribute"); - } - - // Look for method declarations that could be data sources - if (node is MethodDeclarationSyntax method) - { - // Check if method returns IEnumerable or similar - var returnType = method.ReturnType?.ToString(); - if (returnType != null && ( - returnType.Contains("IEnumerable") || - returnType.Contains("IAsyncEnumerable") || - returnType.Contains("Task 0) - { - var firstArg = attribute.ArgumentList.Arguments[0]; - if (firstArg.Expression is LiteralExpressionSyntax literal) - { - methodName = literal.Token.ValueText; - } - } - - if (string.IsNullOrEmpty(methodName)) - { - return null; - } - - // Find the method in the containing type - var containingClass = attribute.Ancestors().OfType().FirstOrDefault(); - if (containingClass == null) - { - return null; - } - - var classSymbol = semanticModel.GetDeclaredSymbol(containingClass) as INamedTypeSymbol; - var targetMethod = classSymbol?.GetMembers(methodName!) - .OfType() - .FirstOrDefault(); - - if (targetMethod == null) - { - return null; - } - - // Only include publicly accessible methods for AOT compatibility - if (!IsAccessibleMethod(targetMethod)) - { - return null; - } - - return new MethodDataSourceInfo - { - TargetMethod = targetMethod, - Location = attribute.GetLocation(), - Usage = MethodUsage.DataSource - }; - } - - private static MethodDataSourceInfo? ExtractFromMethod(MethodDeclarationSyntax method, SemanticModel semanticModel) - { - if (semanticModel.GetDeclaredSymbol(method) is not IMethodSymbol methodSymbol) - { - return null; - } - - // Check if this method could be used as a data source - var returnType = methodSymbol.ReturnType; - if (!IsDataSourceReturnType(returnType)) - { - return null; - } - - // Only include publicly accessible methods for AOT compatibility - if (!IsAccessibleMethod(methodSymbol)) - { - return null; - } - - return new MethodDataSourceInfo - { - TargetMethod = methodSymbol, - Location = method.GetLocation(), - Usage = MethodUsage.DataSource - }; - } - - private static IMethodSymbol? ExtractTargetMethod(ExpressionSyntax methodInfoExpression, SemanticModel semanticModel) - { - // Try to extract method from common patterns: - // - typeof(Class).GetMethod("MethodName") - // - instance.GetType().GetMethod("MethodName") - - if (methodInfoExpression is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: "GetMethod" } memberAccess } invocation) - { - // Extract method name from GetMethod call - if (invocation.ArgumentList.Arguments.Count > 0 && - invocation.ArgumentList.Arguments[0].Expression is LiteralExpressionSyntax literal) - { - var methodName = literal.Token.ValueText; - - // Try to resolve the type - ITypeSymbol? targetType = null; - - if (memberAccess.Expression is InvocationExpressionSyntax { Expression: IdentifierNameSyntax { Identifier.ValueText: "typeof" } } typeofInvocation) - { - // typeof(Class).GetMethod pattern - if (typeofInvocation.ArgumentList.Arguments.Count > 0) - { - targetType = semanticModel.GetTypeInfo(typeofInvocation.ArgumentList.Arguments[0].Expression).Type; - } - } - else - { - // instance.GetType().GetMethod pattern - var getTypeInvocation = memberAccess.Expression as InvocationExpressionSyntax; - if (getTypeInvocation?.Expression is MemberAccessExpressionSyntax { Name.Identifier.ValueText: "GetType" } getTypeMember) - { - targetType = semanticModel.GetTypeInfo(getTypeMember.Expression).Type; - } - } - - if (targetType != null) - { - var method = targetType.GetMembers(methodName) - .OfType() - .FirstOrDefault(); - - // Only return publicly accessible methods - if (method != null && IsAccessibleMethod(method)) - { - return method; - } - } - } - } - - return null; - } - - private static bool IsDataSourceReturnType(ITypeSymbol returnType) - { - var typeName = returnType.ToDisplayString(); - return typeName.Contains("IEnumerable") || - typeName.Contains("IAsyncEnumerable") || - (returnType is INamedTypeSymbol namedType && - namedType.AllInterfaces.Any(i => i.Name.Contains("IEnumerable"))); - } - - private static void GenerateMethodInvokers(SourceProductionContext context, - (ImmutableArray dataSources, ImmutableArray invocations) data) - { - var (dataSources, invocations) = data; - - if (dataSources.IsEmpty && invocations.IsEmpty) - { - return; - } - - var writer = new CodeWriter(); - writer.AppendLine("#nullable enable"); - writer.AppendLine(); - writer.AppendLine("using System;"); - writer.AppendLine("using System.Threading.Tasks;"); - writer.AppendLine("using System.Collections.Generic;"); - writer.AppendLine("using System.Collections;"); - writer.AppendLine(); - writer.AppendLine("namespace TUnit.Generated;"); - writer.AppendLine(); - - GenerateMethodInvokerClass(writer, dataSources, invocations); - - context.AddSource("AotMethodInvokers.g.cs", writer.ToString()); - } - - private static void GenerateMethodInvokerClass(CodeWriter writer, - ImmutableArray dataSources, - ImmutableArray invocations) - { - writer.AppendLine("/// "); - writer.AppendLine("/// AOT-compatible method invocation helpers to replace MethodInfo.Invoke"); - writer.AppendLine("/// "); - writer.AppendLine("public static class AotMethodInvokers"); - writer.AppendLine("{"); - writer.Indent(); - - // Generate registry - GenerateMethodRegistry(writer, dataSources, invocations); - - // Generate invocation helper methods - GenerateInvocationMethods(writer, dataSources, invocations); - - // Generate strongly-typed invokers for each method (avoid duplicates by invoker name) - var allMethods = new HashSet(SymbolEqualityComparer.Default); - foreach (var ds in dataSources) - { - if (!HasUnresolvedTypeParameters(ds.TargetMethod) && IsAccessibleMethod(ds.TargetMethod)) - { - allMethods.Add(ds.TargetMethod); - } - } - foreach (var inv in invocations) - { - if (!HasUnresolvedTypeParameters(inv.TargetMethod) && IsAccessibleMethod(inv.TargetMethod)) - { - allMethods.Add(inv.TargetMethod); - } - } - - var processedInvokerNames = new HashSet(); - foreach (var method in allMethods) - { - var invokerName = GetInvokerMethodName(method); - if (processedInvokerNames.Add(invokerName)) - { - GenerateStronglyTypedInvoker(writer, method); - } - } - - writer.Unindent(); - writer.AppendLine("}"); - } - - private static void GenerateMethodRegistry(CodeWriter writer, - ImmutableArray dataSources, - ImmutableArray invocations) - { - writer.AppendLine("private static readonly Dictionary>> _methodInvokers = new()"); - writer.AppendLine("{"); - writer.Indent(); - - var processedMethods = new HashSet(); - - foreach (var ds in dataSources) - { - // Only include methods that will have implementations generated - if (!HasUnresolvedTypeParameters(ds.TargetMethod) && IsAccessibleMethod(ds.TargetMethod)) - { - var methodKey = GetMethodKey(ds.TargetMethod); - if (processedMethods.Add(methodKey)) - { - var invokerName = GetInvokerMethodName(ds.TargetMethod); - writer.AppendLine($"[\"{methodKey}\"] = {invokerName},"); - } - } - } - - foreach (var inv in invocations) - { - // Only include methods that will have implementations generated - if (!HasUnresolvedTypeParameters(inv.TargetMethod) && IsAccessibleMethod(inv.TargetMethod)) - { - var methodKey = GetMethodKey(inv.TargetMethod); - if (processedMethods.Add(methodKey)) - { - var invokerName = GetInvokerMethodName(inv.TargetMethod); - writer.AppendLine($"[\"{methodKey}\"] = {invokerName},"); - } - } - } - - writer.Unindent(); - writer.AppendLine("};"); - writer.AppendLine(); - } - - private static void GenerateInvocationMethods(CodeWriter writer, - ImmutableArray dataSources, - ImmutableArray invocations) - { - writer.AppendLine("/// "); - writer.AppendLine("/// Invokes a method by key (AOT-safe replacement for MethodInfo.Invoke)"); - writer.AppendLine("/// "); - writer.AppendLine("public static async Task InvokeMethodAsync(string methodKey, object? instance, params object?[]? parameters)"); - writer.AppendLine("{"); - writer.Indent(); - - writer.AppendLine("if (_methodInvokers.TryGetValue(methodKey, out var invoker))"); - writer.AppendLine("{"); - writer.Indent(); - writer.AppendLine("return await invoker(instance, parameters);"); - writer.Unindent(); - writer.AppendLine("}"); - writer.AppendLine(); - writer.AppendLine("throw new global::System.InvalidOperationException($\"No invoker found for method key: {methodKey}\");"); - - writer.Unindent(); - writer.AppendLine("}"); - writer.AppendLine(); - - writer.AppendLine("/// "); - writer.AppendLine("/// Gets the method key for a given method signature"); - writer.AppendLine("/// "); - writer.AppendLine("public static string GetMethodKey(string typeName, string methodName, int parameterCount = 0)"); - writer.AppendLine("{"); - writer.Indent(); - writer.AppendLine("return $\"{typeName}.{methodName}({parameterCount})\";"); - writer.Unindent(); - writer.AppendLine("}"); - writer.AppendLine(); - } - - private static void GenerateStronglyTypedInvoker(CodeWriter writer, IMethodSymbol method) - { - // Note: Pre-filtered for accessibility and type parameters - - var invokerName = GetInvokerMethodName(method); - var returnType = method.ReturnType; - var isAsync = IsAsyncMethod(method); - var isVoid = returnType.SpecialType == SpecialType.System_Void; - - writer.AppendLine("/// "); - writer.AppendLine($"/// Strongly-typed invoker for {method.ContainingType.Name}.{method.Name}"); - writer.AppendLine("/// "); - writer.AppendLine($"private static async Task {invokerName}(object? instance, object?[]? parameters)"); - writer.AppendLine("{"); - writer.Indent(); - - // Generate parameter extraction - var parameters = method.Parameters; - if (parameters.Length > 0) - { - writer.AppendLine("// Extract and convert parameters"); - for (var i = 0; i < parameters.Length; i++) - { - var param = parameters[i]; - var paramType = param.Type.GloballyQualified(); - - // Skip parameters with unresolved type parameters - if (ContainsTypeParameters(param.Type)) - { - writer.AppendLine($"var param{i} = parameters?[{i}]; // Type parameter - using object"); - } - else - { - writer.AppendLine($"var param{i} = parameters != null && parameters.Length > {i} && parameters[{i}] != null"); - writer.AppendLine($" ? ({paramType})parameters[{i}]!"); - writer.AppendLine($" : default({paramType});"); - } - } - writer.AppendLine(); - } - - // Generate method invocation - if (method.IsStatic) - { - writer.Append("var result = "); - if (isAsync) - { - writer.Append("await "); - } - - var containingType = method.ContainingType.GloballyQualified(); - writer.Append($"{containingType}.{method.Name}("); - - if (parameters.Length > 0) - { - writer.Append(string.Join(", ", parameters.Select((_, i) => $"param{i}"))); - } - - writer.AppendLine(");"); - } - else - { - writer.AppendLine($"if (instance is not {method.ContainingType.GloballyQualified()} typedInstance)"); - writer.AppendLine($" throw new global::System.InvalidOperationException($\"Expected instance of type {{typeof({method.ContainingType.GloballyQualified()}).FullName}}, got {{instance?.GetType().FullName ?? \"null\"}}\");"); - writer.AppendLine(); - - writer.Append("var result = "); - if (isAsync) - { - writer.Append("await "); - } - - writer.Append($"typedInstance.{method.Name}("); - - if (parameters.Length > 0) - { - writer.Append(string.Join(", ", parameters.Select((_, i) => $"param{i}"))); - } - - writer.AppendLine(");"); - } - - // Handle return value - if (isVoid) - { - writer.AppendLine("return null;"); - } - else if (isAsync && returnType is INamedTypeSymbol { IsGenericType: true, ConstructedFrom.Name: "Task" } namedReturnType) - { - if (namedReturnType.TypeArguments.Length > 0) - { - // Task - return the result - writer.AppendLine("return result;"); - } - else - { - // Task (void) - return null - writer.AppendLine("return null;"); - } - } - else - { - writer.AppendLine("return result;"); - } - - writer.Unindent(); - writer.AppendLine("}"); - writer.AppendLine(); - } - - private static string GetMethodKey(IMethodSymbol method) - { - var typeName = method.ContainingType.GloballyQualified(); - var methodName = method.Name; - var parameterCount = method.Parameters.Length; - return $"{typeName}.{methodName}({parameterCount})"; - } - - private static string GetInvokerMethodName(IMethodSymbol method) - { - var typeName = GetSafeIdentifierName(method.ContainingType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); - var methodName = GetSafeIdentifierName(method.Name); - - return $"Invoke_{typeName}_{methodName}_{method.Parameters.Length}"; - } - - private static string GetSafeIdentifierName(string name) - { - if (string.IsNullOrEmpty(name)) - { - return "Unknown"; - } - - // Replace all invalid characters with underscores - var result = name - .Replace(".", "_") - .Replace("<", "_") - .Replace(">", "_") - .Replace(",", "_") - .Replace(" ", "_") - .Replace("`", "_") - .Replace("[", "_") - .Replace("]", "_") - .Replace("(", "_") - .Replace(")", "_") - .Replace("+", "_") - .Replace("-", "_") - .Replace("*", "_") - .Replace("/", "_") - .Replace("&", "_") - .Replace("|", "_") - .Replace("^", "_") - .Replace("%", "_") - .Replace("=", "_") - .Replace("!", "_") - .Replace("?", "_") - .Replace(":", "_") - .Replace(";", "_") - .Replace("#", "_") - .Replace("@", "_") - .Replace("$", "_") - .Replace("~", "_") - .Replace("`", "_"); - - // Remove consecutive underscores - while (result.Contains("__")) - { - result = result.Replace("__", "_"); - } - - // Ensure it starts with a letter or underscore - if (result.Length > 0 && !char.IsLetter(result[0]) && result[0] != '_') - { - result = "_" + result; - } - - return result; - } - - private static bool HasUnresolvedTypeParameters(IMethodSymbol method) - { - // Check method type parameters - if (method.TypeParameters.Length > 0) - { - return true; - } - - // Check containing type type parameters - if (method.ContainingType.TypeParameters.Length > 0) - { - return true; - } - - // Check parameter types for type parameters - foreach (var param in method.Parameters) - { - if (ContainsTypeParameters(param.Type)) - { - return true; - } - } - - // Check return type for type parameters - if (ContainsTypeParameters(method.ReturnType)) - { - return true; - } - - return false; - } - - private static bool ContainsTypeParameters(ITypeSymbol type) - { - if (type.TypeKind == TypeKind.TypeParameter) - { - return true; - } - - if (type is INamedTypeSymbol { IsGenericType: true } namedType) - { - foreach (var typeArg in namedType.TypeArguments) - { - if (ContainsTypeParameters(typeArg)) - { - return true; - } - } - } - - return false; - } - - private static bool IsAccessibleMethod(IMethodSymbol method) - { - // Method must be public for AOT compatibility - if (method.DeclaredAccessibility != Accessibility.Public) - { - return false; - } - - // Containing type must also be accessible - var containingType = method.ContainingType; - while (containingType != null) - { - if (containingType.DeclaredAccessibility != Accessibility.Public && - containingType.DeclaredAccessibility != Accessibility.Internal) - { - return false; - } - containingType = containingType.ContainingType; - } - - return true; - } - - private static bool IsAsyncMethod(IMethodSymbol method) - { - var returnType = method.ReturnType; - return returnType.Name == "Task" || - returnType.Name == "ValueTask" || - returnType is INamedTypeSymbol { ConstructedFrom.Name: "Task" } || - returnType is INamedTypeSymbol { ConstructedFrom.Name: "ValueTask" }; - } - - private sealed class MethodDataSourceInfo - { - public required IMethodSymbol TargetMethod { get; init; } - public required Location Location { get; init; } - public required MethodUsage Usage { get; init; } - } - - private sealed class MethodInvocationInfo - { - public required IMethodSymbol TargetMethod { get; init; } - public required Location Location { get; init; } - public required InvocationExpressionSyntax InvocationExpression { get; init; } - } - - private enum MethodUsage - { - DataSource, - DirectInvocation - } -} \ No newline at end of file diff --git a/TUnit.Core.SourceGenerator/Generators/DataSourceHelpersGenerator.cs b/TUnit.Core.SourceGenerator/Generators/DataSourceHelpersGenerator.cs deleted file mode 100644 index 10777bee86..0000000000 --- a/TUnit.Core.SourceGenerator/Generators/DataSourceHelpersGenerator.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System.Collections.Immutable; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using TUnit.Core.SourceGenerator.CodeGenerators.Helpers; -using TUnit.Core.SourceGenerator.Extensions; -using TUnit.Core.SourceGenerator.Models; - -namespace TUnit.Core.SourceGenerator.Generators; - -[Generator] -public class DataSourceHelpersGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var typesWithDataSourceProperties = context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (s, _) => s is ClassDeclarationSyntax, - transform: static (ctx, _) => GetTypeWithDataSourceProperties(ctx)) - .Where(static t => t is not null) - .Collect() - .SelectMany((types, _) => types.DistinctBy(t => t!.Value.TypeSymbol, SymbolEqualityComparer.Default)); - - // Generate individual files for each unique type - context.RegisterSourceOutput(typesWithDataSourceProperties, (spc, type) => { if (type != null) GenerateIndividualDataSourceHelper(spc, type); }); - } - - private static TypeWithDataSourceProperties? GetTypeWithDataSourceProperties(GeneratorSyntaxContext context) - { - var classDeclaration = (ClassDeclarationSyntax)context.Node; - var semanticModel = context.SemanticModel; - - if (semanticModel.GetDeclaredSymbol(classDeclaration) is not INamedTypeSymbol typeSymbol) - { - return null; - } - - var propertiesWithDataSource = typeSymbol.GetMembers() - .OfType() - .Where(p => p.DeclaredAccessibility == Accessibility.Public && - (p.SetMethod != null || p.GetMethod != null) && // Include properties with getter (for init-only) - p.GetAttributes().Any(a => DataSourceAttributeHelper.IsDataSourceAttribute(a.AttributeClass))) - .Select(p => new PropertyWithDataSource - { - Property = p, - DataSourceAttribute = p.GetAttributes() - .First(a => DataSourceAttributeHelper.IsDataSourceAttribute(a.AttributeClass)) - }) - .ToList(); - - if (!propertiesWithDataSource.Any()) - { - return null; - } - - return new TypeWithDataSourceProperties - { - TypeSymbol = typeSymbol, - Properties = propertiesWithDataSource - }; - } - - private static void GenerateIndividualDataSourceHelper(SourceProductionContext context, TypeWithDataSourceProperties? type) - { - // Skip if null, no properties or abstract class - if (type == null || !type.Value.Properties.Any() || type.Value.TypeSymbol.IsAbstract) - { - return; - } - - var fullyQualifiedType = type.Value.TypeSymbol.GloballyQualified(); - var safeName = GetSafeTypeName(type.Value.TypeSymbol); - var fileName = $"{safeName}_DataSourceHelper.g.cs"; - - var sb = new StringBuilder(); - - sb.AppendLine("// "); - sb.AppendLine("#pragma warning disable"); - sb.AppendLine("using System;"); - sb.AppendLine("using System.Runtime.CompilerServices;"); - sb.AppendLine("using System.Threading.Tasks;"); - sb.AppendLine("using TUnit.Core;"); - sb.AppendLine(); - sb.AppendLine("namespace TUnit.Core.Generated;"); - sb.AppendLine(); - - // Generate individual module initializer for this type - sb.AppendLine($"internal static class {safeName}_DataSourceInitializer"); - sb.AppendLine("{"); - sb.AppendLine(" [ModuleInitializer]"); - sb.AppendLine(" public static void Initialize()"); - sb.AppendLine(" {"); - sb.AppendLine($" global::TUnit.Core.Helpers.DataSourceHelpers.RegisterPropertyInitializer<{fullyQualifiedType}>(InitializePropertiesAsync_{safeName});"); - sb.AppendLine(" }"); - - // Generate the property initialization method for this specific type - GeneratePropertyInitializationMethod(sb, type.Value, safeName, fullyQualifiedType); - - sb.AppendLine("}"); - - context.AddSource(fileName, sb.ToString()); - } - - private static string GetSafeTypeName(INamedTypeSymbol typeSymbol) - { - var fullyQualifiedType = typeSymbol.GloballyQualified(); - return fullyQualifiedType - .Replace("global::", "") - .Replace(".", "_") - .Replace("<", "_") - .Replace(">", "_") - .Replace(",", "_") - .Replace(" ", "") - .Replace("`", "_") - .Replace("+", "_"); - } - - private static void GeneratePropertyInitializationMethod(StringBuilder sb, TypeWithDataSourceProperties type, string safeName, string fullyQualifiedType) - { - var settableProperties = type.Properties.Where(p => p.Property.SetMethod != null && !p.Property.SetMethod.IsInitOnly).ToList(); - var initOnlyProperties = type.Properties.Where(p => p.Property.SetMethod?.IsInitOnly == true).ToList(); - - sb.AppendLine($" public static async Task InitializePropertiesAsync_{safeName}({fullyQualifiedType} instance, global::TUnit.Core.MethodMetadata testInformation, string testSessionId)"); - sb.AppendLine(" {"); - - // Handle init-only properties with reflection - if (initOnlyProperties.Any()) - { - sb.AppendLine(" // Set init-only properties that are null using reflection"); - foreach (var propInfo in initOnlyProperties) - { - var property = propInfo.Property; - var propertyName = property.Name; - - if (!property.Type.IsValueType) - { - sb.AppendLine($" if (instance.{propertyName} == null)"); - sb.AppendLine(" {"); - } - else - { - sb.AppendLine(" {"); - } - - sb.AppendLine($" var value = await global::TUnit.Core.Helpers.DataSourceHelpers.ResolveDataSourcePropertyAsync("); - sb.AppendLine($" instance, \"{propertyName}\", testInformation, testSessionId);"); - sb.AppendLine($" var backingField = instance.GetType().GetField(\"<{propertyName}>k__BackingField\", "); - sb.AppendLine(" global::System.Reflection.BindingFlags.Instance | global::System.Reflection.BindingFlags.NonPublic);"); - sb.AppendLine(" backingField?.SetValue(instance, value);"); - sb.AppendLine(" }"); - } - sb.AppendLine(); - } - - // Handle settable properties - foreach (var propInfo in settableProperties) - { - var property = propInfo.Property; - var propertyName = property.Name; - - if (property.IsStatic) - { - // Generate static property initialization - sb.AppendLine($" // Initialize static property {propertyName}"); - sb.AppendLine($" if ({fullyQualifiedType}.{propertyName} == default)"); - sb.AppendLine(" {"); - sb.AppendLine($" var value = await global::TUnit.Core.Helpers.DataSourceHelpers.ResolveDataSourcePropertyAsync("); - sb.AppendLine($" instance, \"{propertyName}\", testInformation, testSessionId);"); - sb.AppendLine($" {fullyQualifiedType}.{propertyName} = ({property.Type.GloballyQualified()})value;"); - sb.AppendLine(" }"); - } - else - { - GeneratePropertyInitialization(sb, propInfo); - } - sb.AppendLine(); - } - - sb.AppendLine(" }"); - } - - private static void GeneratePropertyInitialization(StringBuilder sb, PropertyWithDataSource propInfo) - { - var property = propInfo.Property; - var attr = propInfo.DataSourceAttribute; - var propertyName = property.Name; - - if (attr.AttributeClass == null) - { - return; - } - - sb.AppendLine($" // Initialize {propertyName} property"); - sb.AppendLine($" if (instance.{propertyName} == default)"); - sb.AppendLine(" {"); - sb.AppendLine($" var value = await global::TUnit.Core.Helpers.DataSourceHelpers.ResolveDataSourcePropertyAsync("); - sb.AppendLine($" instance, \"{propertyName}\", testInformation, testSessionId);"); - sb.AppendLine($" instance.{propertyName} = ({property.Type.GloballyQualified()})value;"); - sb.AppendLine(" }"); - sb.AppendLine(); - } -} diff --git a/TUnit.Core/Helpers/DataSourceHelpers.cs b/TUnit.Core/Helpers/DataSourceHelpers.cs index f9a57ebffd..2ad1d53134 100644 --- a/TUnit.Core/Helpers/DataSourceHelpers.cs +++ b/TUnit.Core/Helpers/DataSourceHelpers.cs @@ -292,41 +292,6 @@ public static T InvokeIfFunc(object? value) return [() => Task.FromResult(InvokeIfFunc(data))]; } - - /// - /// AOT-compatible runtime dispatcher for data source property initialization. - /// This will be populated by the generated DataSourceHelpers class. - /// - private static readonly Dictionary> PropertyInitializers = new(); - - /// - /// Register a type-specific property initializer (called by generated code) - /// - public static void RegisterPropertyInitializer(Func initializer) - { - PropertyInitializers[typeof(T)] = (instance, testInfo, sessionId) => - initializer((T)instance, testInfo, sessionId); - } - - /// - /// Initialize data source properties on an instance using registered type-specific helpers - /// - public static async Task InitializeDataSourcePropertiesAsync(object? instance, MethodMetadata testInformation, string testSessionId) - { - if (instance == null) - { - return; - } - - var instanceType = instance.GetType(); - - if (PropertyInitializers.TryGetValue(instanceType, out var initializer)) - { - await initializer(instance, testInformation, testSessionId); - } - // If no initializer is registered, the type has no data source properties - } - public static object?[] ToObjectArray(this object? item) { item = InvokeIfFunc(item); @@ -618,15 +583,4 @@ public static void RegisterTypeCreator(Func> return null; } - - /// - /// Resolves a data source property value at runtime for an existing instance. - /// This is used when we need to set init-only properties via reflection. - /// - public static Task ResolveDataSourcePropertyAsync(object instance, string propertyName, MethodMetadata testInformation, string testSessionId) - { - // For now, return a default value - the runtime resolution is complex - // In practice, this should be rare since most data sources can be resolved at compile time - return Task.FromResult(null); - } } 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 9309930a6b..8ee1c045a8 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 @@ -2006,7 +2006,6 @@ namespace .Helpers public static class DataSourceHelpers { public static <.>[] HandleTupleValue(object? value, bool shouldUnwrap) { } - public static . InitializeDataSourcePropertiesAsync(object? instance, .MethodMetadata testInformation, string testSessionId) { } public static object? InvokeIfFunc(object? value) { } public static T InvokeIfFunc(object? value) { } public static bool IsTuple(object? obj) { } @@ -2014,12 +2013,10 @@ namespace .Helpers public static . ProcessDataSourceResultGeneric(T data) { } public static . ProcessEnumerableDataSource(. enumerable) { } public static <.>[] ProcessTestDataSource(T data, int expectedParameterCount = -1) { } - public static void RegisterPropertyInitializer( initializer) { } public static void RegisterTypeCreator(<.MethodMetadata, string, .> creator) { } [.("Data source resolution may require dynamic code generation")] [.("Property types are resolved through reflection")] public static . ResolveDataSourceForPropertyAsync([.(..None | ..PublicParameterlessConstructor | ..PublicFields | ..NonPublicFields | ..PublicProperties)] containingType, string propertyName, .MethodMetadata testInformation, string testSessionId) { } - public static . ResolveDataSourcePropertyAsync(object instance, string propertyName, .MethodMetadata testInformation, string testSessionId) { } public static object?[] ToObjectArray(this object? item) { } public static object?[] ToObjectArrayWithTypes(this object? item, []? expectedTypes) { } [return: .(new string[] { 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 5c7f7cea65..f3e4cd113f 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 @@ -2006,7 +2006,6 @@ namespace .Helpers public static class DataSourceHelpers { public static <.>[] HandleTupleValue(object? value, bool shouldUnwrap) { } - public static . InitializeDataSourcePropertiesAsync(object? instance, .MethodMetadata testInformation, string testSessionId) { } public static object? InvokeIfFunc(object? value) { } public static T InvokeIfFunc(object? value) { } public static bool IsTuple(object? obj) { } @@ -2014,12 +2013,10 @@ namespace .Helpers public static . ProcessDataSourceResultGeneric(T data) { } public static . ProcessEnumerableDataSource(. enumerable) { } public static <.>[] ProcessTestDataSource(T data, int expectedParameterCount = -1) { } - public static void RegisterPropertyInitializer( initializer) { } public static void RegisterTypeCreator(<.MethodMetadata, string, .> creator) { } [.("Data source resolution may require dynamic code generation")] [.("Property types are resolved through reflection")] public static . ResolveDataSourceForPropertyAsync([.(..None | ..PublicParameterlessConstructor | ..PublicFields | ..NonPublicFields | ..PublicProperties)] containingType, string propertyName, .MethodMetadata testInformation, string testSessionId) { } - public static . ResolveDataSourcePropertyAsync(object instance, string propertyName, .MethodMetadata testInformation, string testSessionId) { } public static object?[] ToObjectArray(this object? item) { } public static object?[] ToObjectArrayWithTypes(this object? item, []? expectedTypes) { } [return: .(new string[] { 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 c50d5ed77e..e054e1fa10 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 @@ -1887,7 +1887,6 @@ namespace .Helpers public static class DataSourceHelpers { public static <.>[] HandleTupleValue(object? value, bool shouldUnwrap) { } - public static . InitializeDataSourcePropertiesAsync(object? instance, .MethodMetadata testInformation, string testSessionId) { } public static object? InvokeIfFunc(object? value) { } public static T InvokeIfFunc(object? value) { } public static bool IsTuple(object? obj) { } @@ -1895,10 +1894,8 @@ namespace .Helpers public static . ProcessDataSourceResultGeneric(T data) { } public static . ProcessEnumerableDataSource(. enumerable) { } public static <.>[] ProcessTestDataSource(T data, int expectedParameterCount = -1) { } - public static void RegisterPropertyInitializer( initializer) { } public static void RegisterTypeCreator(<.MethodMetadata, string, .> creator) { } public static . ResolveDataSourceForPropertyAsync( containingType, string propertyName, .MethodMetadata testInformation, string testSessionId) { } - public static . ResolveDataSourcePropertyAsync(object instance, string propertyName, .MethodMetadata testInformation, string testSessionId) { } public static object?[] ToObjectArray(this object? item) { } public static object?[] ToObjectArrayWithTypes(this object? item, []? expectedTypes) { } [return: .(new string[] { diff --git a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet8_0.verified.txt index f5f272a736..ab2d72850c 100644 --- a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -1,13 +1,5 @@ [assembly: .(".NETCoreApp,Version=v8.0", FrameworkDisplayName=".NET 8.0")] namespace -{ - public static class AotMethodInvokers - { - public static string GetMethodKey(string typeName, string methodName, int parameterCount = 0) { } - public static . InvokeMethodAsync(string methodKey, object? instance, params object?[]? parameters) { } - } -} -namespace { public class BrowserTest : .PlaywrightTest { diff --git a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet9_0.verified.txt index f03f4d7461..1455bbb755 100644 --- a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -1,13 +1,5 @@ [assembly: .(".NETCoreApp,Version=v9.0", FrameworkDisplayName=".NET 9.0")] namespace -{ - public static class AotMethodInvokers - { - public static string GetMethodKey(string typeName, string methodName, int parameterCount = 0) { } - public static . InvokeMethodAsync(string methodKey, object? instance, params object?[]? parameters) { } - } -} -namespace { public class BrowserTest : .PlaywrightTest { diff --git a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.Net4_7.verified.txt index 6fcb740bfe..9378dd1f79 100644 --- a/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Playwright_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -1,13 +1,5 @@ [assembly: .(".NETStandard,Version=v2.0", FrameworkDisplayName=".NET Standard 2.0")] namespace -{ - public static class AotMethodInvokers - { - public static string GetMethodKey(string typeName, string methodName, int parameterCount = 0) { } - public static . InvokeMethodAsync(string methodKey, object? instance, params object?[]? parameters) { } - } -} -namespace { public class BrowserTest : .PlaywrightTest {