Skip to content
Merged
252 changes: 168 additions & 84 deletions src/TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
private const string InstanceMethodDataSourceAttributeName = "global::TUnit.Core.InstanceMethodDataSourceAttribute";

private static readonly DiagnosticDescriptor GenerationErrorDescriptor = new(
"TUNIT0999",

Check warning on line 29 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (tr-TR)

'TUNIT0999' kuralını içeren çözümleyici projesi için çözümleyici yayını izlemeyi etkinleştir (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 29 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / AOT (windows-latest)

Check warning on line 29 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 29 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 29 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

"Source Generation Error",
"Failed to generate test metadata for {0}.{1}: {2}",
"TUnit",
Expand Down Expand Up @@ -7700,7 +7700,7 @@
if (!inferredTypes.ContainsKey(name))
{
inferredTypes[name] = compilation.GetSpecialType(SpecialType.System_Object);
}

Check warning on line 7703 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (tr-TR)

Olası bir null başvurunun başvurma işlemi.

Check warning on line 7703 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / AOT (windows-latest)

Dereference of a possibly null reference.

Check warning on line 7703 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Dereference of a possibly null reference.

Check warning on line 7703 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Dereference of a possibly null reference.

Check warning on line 7703 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Dereference of a possibly null reference.

Check warning on line 7703 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Dereference of a possibly null reference.

Check warning on line 7703 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Dereference of a possibly null reference.

Check warning on line 7703 in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Dereference of a possibly null reference.
}
}

Expand Down Expand Up @@ -8361,7 +8361,7 @@
/// resolution on the target type would discard a base overload once a derived one is applicable
/// (https://github.com/thomhurst/TUnit/issues/7202).
/// </summary>
private static ISymbol? ResolveMethodDataSourceMember(ITypeSymbol targetType, string memberName, AttributeData attr, Compilation compilation)
internal static ISymbol? ResolveMethodDataSourceMember(ITypeSymbol targetType, string memberName, AttributeData attr, Compilation compilation)
{
var argumentsProperty = attr.NamedArguments.FirstOrDefault(x => x.Key == "Arguments");
var arguments = argumentsProperty is { Key: not null, Value: { IsNull: false, Kind: TypedConstantKind.Array } }
Expand Down
14 changes: 14 additions & 0 deletions src/TUnit.Core/Helpers/CastHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ internal static bool TryConvertTrailingArray(Type arrayType, object value, out o
/// 1. Fast path: null handling, direct cast, nullable unwrapping
/// 2. AOT-safe: AotConverterRegistry, primitives, enums
/// 3. Reflection fallback: custom operators, arrays (throws in AOT)
/// Built-in decimal-to-integral conversions round midpoint values to even and throw
/// OverflowException for out-of-range values. Explicitly registered converters take priority.
/// </summary>
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.")]
public static object? Cast([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods)] Type type, object? value)
Expand Down Expand Up @@ -197,6 +199,18 @@ private static bool TryAotSafeConversion(Type targetType, Type sourceType, objec
return true;
}

// Decimal operators truncate integral results, whereas the primitive fallback
// rounds to nearest even. Generated numeric converters use this same policy;
// explicitly registered converters retain priority above.
if (value is decimal && (targetType == typeof(sbyte) || targetType == typeof(byte) ||
targetType == typeof(short) || targetType == typeof(ushort) ||
targetType == typeof(int) || targetType == typeof(uint) ||
targetType == typeof(long) || targetType == typeof(ulong)))
{
result = Convert.ChangeType(value, targetType);
return true;
Comment thread
thomhurst marked this conversation as resolved.
Resolved
}

// Handle IConvertible primitives (int, string, double, etc.)
if (value is IConvertible && targetType.IsPrimitive)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// <auto-generated/>
#pragma warning disable

#nullable enable
using System;
using TUnit.Core.Converters;
namespace TUnit.Generated
{
internal sealed class AotConverter_0 : IAotConverter
{
public Type SourceType => typeof(global::ClassValue);
public Type TargetType => typeof(int);
public object? Convert(object? value)
{
if (value == null) return null;
if (value is int targetTypedValue)
{
return targetTypedValue;
}
if (value is global::ClassValue sourceTypedValue)
{
int converted = sourceTypedValue;
return converted;
}
return value; // Return original value if type doesn't match
}
}
internal sealed class AotConverter_1 : IAotConverter
{
public Type SourceType => typeof(global::ScalarValue);
public Type TargetType => typeof(int);
public object? Convert(object? value)
{
if (value == null) return null;
if (value is int targetTypedValue)
{
return targetTypedValue;
}
if (value is global::ScalarValue sourceTypedValue)
{
int converted = sourceTypedValue;
return converted;
}
return value; // Return original value if type doesn't match
}
}
internal sealed class AotConverter_2 : IAotConverter
{
public Type SourceType => typeof(global::TupleValue);
public Type TargetType => typeof(global::System.ValueTuple<int, int>);
public object? Convert(object? value)
{
if (value == null) return null;
if (value is global::System.ValueTuple<int, int> targetTypedValue)
{
return targetTypedValue;
}
if (value is global::TupleValue sourceTypedValue)
{
global::System.ValueTuple<int, int> converted = (global::System.ValueTuple<int, int>)sourceTypedValue;
return converted;
}
return value; // Return original value if type doesn't match
}
}
internal sealed class AotConverter_3 : IAotConverter
{
public Type SourceType => typeof(global::ArrayValue);
public Type TargetType => typeof(int);
public object? Convert(object? value)
{
if (value == null) return null;
if (value is int targetTypedValue)
{
return targetTypedValue;
}
if (value is global::ArrayValue sourceTypedValue)
{
int converted = sourceTypedValue;
return converted;
}
return value; // Return original value if type doesn't match
}
}
internal sealed class AotConverter_4 : IAotConverter
{
public Type SourceType => typeof(global::AsyncValue);
public Type TargetType => typeof(int);
public object? Convert(object? value)
{
if (value == null) return null;
if (value is int targetTypedValue)
{
return targetTypedValue;
}
if (value is global::AsyncValue sourceTypedValue)
{
int converted = (int)sourceTypedValue;
return converted;
}
return value; // Return original value if type doesn't match
}
}
internal sealed class AotConverter_5 : IAotConverter
{
public Type SourceType => typeof(global::ParameterValue);
public Type TargetType => typeof(int);
public object? Convert(object? value)
{
if (value == null) return null;
if (value is int targetTypedValue)
{
return targetTypedValue;
}
if (value is global::ParameterValue sourceTypedValue)
{
int converted = sourceTypedValue;
return converted;
}
return value; // Return original value if type doesn't match
}
}
internal static partial class TUnit_ConverterRegistration
{
static readonly int _r_AotConverter_0 = global::TUnit.Core.Converters.AotConverterRegistry.Register(new AotConverter_0());
static readonly int _r_AotConverter_1 = global::TUnit.Core.Converters.AotConverterRegistry.Register(new AotConverter_1());
static readonly int _r_AotConverter_2 = global::TUnit.Core.Converters.AotConverterRegistry.Register(new AotConverter_2());
static readonly int _r_AotConverter_3 = global::TUnit.Core.Converters.AotConverterRegistry.Register(new AotConverter_3());
static readonly int _r_AotConverter_4 = global::TUnit.Core.Converters.AotConverterRegistry.Register(new AotConverter_4());
static readonly int _r_AotConverter_5 = global::TUnit.Core.Converters.AotConverterRegistry.Register(new AotConverter_5());
}
}
Loading
Loading