diff --git a/TUnit.Core.SourceGenerator.Tests/AotConverterGeneratorTests.GeneratesCode.verified.txt b/TUnit.Core.SourceGenerator.Tests/AotConverterGeneratorTests.GeneratesCode.verified.txt new file mode 100644 index 0000000000..427afed554 --- /dev/null +++ b/TUnit.Core.SourceGenerator.Tests/AotConverterGeneratorTests.GeneratesCode.verified.txt @@ -0,0 +1,1270 @@ +// +#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::TUnit.TestProject.AllDataSourcesCombinedTests.DataSource1); + 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::TUnit.TestProject.AllDataSourcesCombinedTests.DataSource1 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_1 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.AllDataSourcesCombinedTests.DataSource2); + 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::TUnit.TestProject.AllDataSourcesCombinedTests.DataSource2 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_2 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.AllDataSourcesCombinedTests.DataSource3); + 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::TUnit.TestProject.AllDataSourcesCombinedTests.DataSource3 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_3 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.AllDataSourcesCombinedTestsVerification.DataSource1); + 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::TUnit.TestProject.AllDataSourcesCombinedTestsVerification.DataSource1 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_4 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.AllDataSourcesCombinedTestsVerification.DataSource2); + 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::TUnit.TestProject.AllDataSourcesCombinedTestsVerification.DataSource2 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_5 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.AllDataSourcesCombinedTestsVerification.DataSource3); + 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::TUnit.TestProject.AllDataSourcesCombinedTestsVerification.DataSource3 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_6 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.ArgumentsWithClassDataSourceTests.IntDataSource1); + 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::TUnit.TestProject.ArgumentsWithClassDataSourceTests.IntDataSource1 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_7 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.ArgumentsWithClassDataSourceTests.IntDataSource2); + 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::TUnit.TestProject.ArgumentsWithClassDataSourceTests.IntDataSource2 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_8 : IAotConverter +{ + public Type SourceType => typeof(int); + public Type TargetType => typeof(global::TUnit.TestProject.ExplicitInteger); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.ExplicitInteger targetTypedValue) + { + return targetTypedValue; + } + if (value is int sourceTypedValue) + { + return (global::TUnit.TestProject.ExplicitInteger)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_9 : IAotConverter +{ + public Type SourceType => typeof(int); + public Type TargetType => typeof(global::TUnit.TestProject.ImplicitInteger); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.ImplicitInteger targetTypedValue) + { + return targetTypedValue; + } + if (value is int sourceTypedValue) + { + return (global::TUnit.TestProject.ImplicitInteger)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_10 : IAotConverter +{ + public Type SourceType => typeof(byte); + public Type TargetType => typeof(byte?); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is byte targetTypedValue) + { + return targetTypedValue; + } + if (value is byte sourceTypedValue) + { + return (byte?)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_11 : IAotConverter +{ + public Type SourceType => typeof(byte?); + public Type TargetType => typeof(byte); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is byte targetTypedValue) + { + return targetTypedValue; + } + if (value is byte sourceTypedValue) + { + return (byte)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_12 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.ClassDataSourceEnumerableTest.EnumerableDataSource); + public Type TargetType => typeof(string); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is string targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.ClassDataSourceEnumerableTest.EnumerableDataSource sourceTypedValue) + { + return (string)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_13 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.ClassDataSourceWithMethodDataSourceTests.DataSource1); + 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::TUnit.TestProject.ClassDataSourceWithMethodDataSourceTests.DataSource1 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_14 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.ClassDataSourceWithMethodDataSourceTests.DataSource2); + 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::TUnit.TestProject.ClassDataSourceWithMethodDataSourceTests.DataSource2 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_15 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.ClassDataSourceWithMethodDataSourceTests.DataSource3); + 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::TUnit.TestProject.ClassDataSourceWithMethodDataSourceTests.DataSource3 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_16 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.ComprehensiveCountTest.ClassData); + public Type TargetType => typeof(string); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is string targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.ComprehensiveCountTest.ClassData sourceTypedValue) + { + return (string)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_17 : IAotConverter +{ + public Type SourceType => typeof(bool); + public Type TargetType => typeof(bool?); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is bool targetTypedValue) + { + return targetTypedValue; + } + if (value is bool sourceTypedValue) + { + return (bool?)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_18 : IAotConverter +{ + public Type SourceType => typeof(bool?); + public Type TargetType => typeof(bool); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is bool targetTypedValue) + { + return targetTypedValue; + } + if (value is bool sourceTypedValue) + { + return (bool)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_19 : IAotConverter +{ + public Type SourceType => typeof(byte); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is byte sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_20 : IAotConverter +{ + public Type SourceType => typeof(sbyte); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is sbyte sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_21 : IAotConverter +{ + public Type SourceType => typeof(short); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is short sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_22 : IAotConverter +{ + public Type SourceType => typeof(ushort); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is ushort sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_23 : IAotConverter +{ + public Type SourceType => typeof(char); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is char sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_24 : IAotConverter +{ + public Type SourceType => typeof(int); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is int sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_25 : IAotConverter +{ + public Type SourceType => typeof(uint); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is uint sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_26 : IAotConverter +{ + public Type SourceType => typeof(long); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is long sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_27 : IAotConverter +{ + public Type SourceType => typeof(ulong); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is ulong sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_28 : IAotConverter +{ + public Type SourceType => typeof(float); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is float sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_29 : IAotConverter +{ + public Type SourceType => typeof(double); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is double sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_30 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(byte); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is byte targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (byte)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_31 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(sbyte); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is sbyte targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (sbyte)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_32 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(char); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is char targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (char)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_33 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(short); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is short targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (short)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_34 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(ushort); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is ushort targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (ushort)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_35 : IAotConverter +{ + public Type SourceType => typeof(decimal); + 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 decimal sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_36 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(uint); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is uint targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (uint)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_37 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(long); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is long targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (long)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_38 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(ulong); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is ulong targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (ulong)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_39 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(float); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is float targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (float)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_40 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(double); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is double targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (double)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_41 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.TestEnum); + public Type TargetType => typeof(global::TUnit.TestProject.TestEnum?); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.TestEnum targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.TestEnum sourceTypedValue) + { + return (global::TUnit.TestProject.TestEnum?)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_42 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.TestEnum?); + public Type TargetType => typeof(global::TUnit.TestProject.TestEnum); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.TestEnum targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.TestEnum sourceTypedValue) + { + return (global::TUnit.TestProject.TestEnum)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_43 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedDataSourceBugTest.ClassData1); + 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::TUnit.TestProject.MixedDataSourceBugTest.ClassData1 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_44 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedDataSourceBugTest.ClassData2); + 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::TUnit.TestProject.MixedDataSourceBugTest.ClassData2 sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_45 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedMatrixTests.Enum4); + public Type TargetType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion1); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion1 targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.MixedMatrixTests.Enum4 sourceTypedValue) + { + return (global::TUnit.TestProject.MixedMatrixTestsUnion1)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_46 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion1); + public Type TargetType => typeof(global::TUnit.TestProject.MixedMatrixTests.Enum4); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.MixedMatrixTests.Enum4 targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion1 sourceTypedValue) + { + return (global::TUnit.TestProject.MixedMatrixTests.Enum4)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_47 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedMatrixTests.Enum5); + public Type TargetType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion1); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion1 targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.MixedMatrixTests.Enum5 sourceTypedValue) + { + return (global::TUnit.TestProject.MixedMatrixTestsUnion1)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_48 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion1); + public Type TargetType => typeof(global::TUnit.TestProject.MixedMatrixTests.Enum5); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.MixedMatrixTests.Enum5 targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion1 sourceTypedValue) + { + return (global::TUnit.TestProject.MixedMatrixTests.Enum5)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_49 : IAotConverter +{ + public Type SourceType => typeof(string); + public Type TargetType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion1); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion1 targetTypedValue) + { + return targetTypedValue; + } + if (value is string sourceTypedValue) + { + return (global::TUnit.TestProject.MixedMatrixTestsUnion1)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_50 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion1); + public Type TargetType => typeof(string); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is string targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion1 sourceTypedValue) + { + return (string)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_51 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedMatrixTests.Enum4); + public Type TargetType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion2); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion2 targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.MixedMatrixTests.Enum4 sourceTypedValue) + { + return (global::TUnit.TestProject.MixedMatrixTestsUnion2)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_52 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion2); + public Type TargetType => typeof(global::TUnit.TestProject.MixedMatrixTests.Enum4); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.MixedMatrixTests.Enum4 targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion2 sourceTypedValue) + { + return (global::TUnit.TestProject.MixedMatrixTests.Enum4)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_53 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedMatrixTests.Enum5); + public Type TargetType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion2); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion2 targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.MixedMatrixTests.Enum5 sourceTypedValue) + { + return (global::TUnit.TestProject.MixedMatrixTestsUnion2)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_54 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion2); + public Type TargetType => typeof(global::TUnit.TestProject.MixedMatrixTests.Enum5); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.MixedMatrixTests.Enum5 targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion2 sourceTypedValue) + { + return (global::TUnit.TestProject.MixedMatrixTests.Enum5)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_55 : IAotConverter +{ + public Type SourceType => typeof(string); + public Type TargetType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion2); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion2 targetTypedValue) + { + return targetTypedValue; + } + if (value is string sourceTypedValue) + { + return (global::TUnit.TestProject.MixedMatrixTestsUnion2)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_56 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.MixedMatrixTestsUnion2); + public Type TargetType => typeof(string); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is string targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.MixedMatrixTestsUnion2 sourceTypedValue) + { + return (string)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_57 : IAotConverter +{ + public Type SourceType => typeof(decimal); + public Type TargetType => typeof(decimal?); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (decimal?)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_58 : IAotConverter +{ + public Type SourceType => typeof(decimal?); + public Type TargetType => typeof(decimal); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is decimal targetTypedValue) + { + return targetTypedValue; + } + if (value is decimal sourceTypedValue) + { + return (decimal)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_59 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.TestCountVerificationTests.TestDataSource); + 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::TUnit.TestProject.TestCountVerificationTests.TestDataSource sourceTypedValue) + { + return (int)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_60 : IAotConverter +{ + public Type SourceType => typeof(int); + public Type TargetType => typeof(global::TUnit.TestProject.Bugs._2757.Foo); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.Bugs._2757.Foo targetTypedValue) + { + return targetTypedValue; + } + if (value is int sourceTypedValue) + { + return (global::TUnit.TestProject.Bugs._2757.Foo)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_61 : IAotConverter +{ + public Type SourceType => typeof(global::System.ValueTuple); + public Type TargetType => typeof(global::TUnit.TestProject.Bugs._2798.Foo); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.Bugs._2798.Foo targetTypedValue) + { + return targetTypedValue; + } + if (value is global::System.ValueTuple sourceTypedValue) + { + return (global::TUnit.TestProject.Bugs._2798.Foo)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_62 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.Bugs._3185.FlagMock); + public Type TargetType => typeof(global::TUnit.TestProject.Bugs._3185.FlagMock?); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.Bugs._3185.FlagMock targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.Bugs._3185.FlagMock sourceTypedValue) + { + return (global::TUnit.TestProject.Bugs._3185.FlagMock?)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_63 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.Bugs._3185.FlagMock?); + public Type TargetType => typeof(global::TUnit.TestProject.Bugs._3185.FlagMock); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.Bugs._3185.FlagMock targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.Bugs._3185.FlagMock sourceTypedValue) + { + return (global::TUnit.TestProject.Bugs._3185.FlagMock)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_64 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.Bugs._3185.RegularEnum); + public Type TargetType => typeof(global::TUnit.TestProject.Bugs._3185.RegularEnum?); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.Bugs._3185.RegularEnum targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.Bugs._3185.RegularEnum sourceTypedValue) + { + return (global::TUnit.TestProject.Bugs._3185.RegularEnum?)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal sealed class AotConverter_65 : IAotConverter +{ + public Type SourceType => typeof(global::TUnit.TestProject.Bugs._3185.RegularEnum?); + public Type TargetType => typeof(global::TUnit.TestProject.Bugs._3185.RegularEnum); + public object? Convert(object? value) + { + if (value == null) return null; + if (value is global::TUnit.TestProject.Bugs._3185.RegularEnum targetTypedValue) + { + return targetTypedValue; + } + if (value is global::TUnit.TestProject.Bugs._3185.RegularEnum sourceTypedValue) + { + return (global::TUnit.TestProject.Bugs._3185.RegularEnum)sourceTypedValue; + } + return value; // Return original value if type doesn't match + } +} +internal static class AotConverterRegistration +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + [global::System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA2255:The 'ModuleInitializer' attribute should not be used in libraries", + Justification = "Test framework needs to register AOT converters for conversion operators")] + public static void Initialize() + { + AotConverterRegistry.Register(new AotConverter_0()); + AotConverterRegistry.Register(new AotConverter_1()); + AotConverterRegistry.Register(new AotConverter_2()); + AotConverterRegistry.Register(new AotConverter_3()); + AotConverterRegistry.Register(new AotConverter_4()); + AotConverterRegistry.Register(new AotConverter_5()); + AotConverterRegistry.Register(new AotConverter_6()); + AotConverterRegistry.Register(new AotConverter_7()); + AotConverterRegistry.Register(new AotConverter_8()); + AotConverterRegistry.Register(new AotConverter_9()); + AotConverterRegistry.Register(new AotConverter_10()); + AotConverterRegistry.Register(new AotConverter_11()); + AotConverterRegistry.Register(new AotConverter_12()); + AotConverterRegistry.Register(new AotConverter_13()); + AotConverterRegistry.Register(new AotConverter_14()); + AotConverterRegistry.Register(new AotConverter_15()); + AotConverterRegistry.Register(new AotConverter_16()); + AotConverterRegistry.Register(new AotConverter_17()); + AotConverterRegistry.Register(new AotConverter_18()); + AotConverterRegistry.Register(new AotConverter_19()); + AotConverterRegistry.Register(new AotConverter_20()); + AotConverterRegistry.Register(new AotConverter_21()); + AotConverterRegistry.Register(new AotConverter_22()); + AotConverterRegistry.Register(new AotConverter_23()); + AotConverterRegistry.Register(new AotConverter_24()); + AotConverterRegistry.Register(new AotConverter_25()); + AotConverterRegistry.Register(new AotConverter_26()); + AotConverterRegistry.Register(new AotConverter_27()); + AotConverterRegistry.Register(new AotConverter_28()); + AotConverterRegistry.Register(new AotConverter_29()); + AotConverterRegistry.Register(new AotConverter_30()); + AotConverterRegistry.Register(new AotConverter_31()); + AotConverterRegistry.Register(new AotConverter_32()); + AotConverterRegistry.Register(new AotConverter_33()); + AotConverterRegistry.Register(new AotConverter_34()); + AotConverterRegistry.Register(new AotConverter_35()); + AotConverterRegistry.Register(new AotConverter_36()); + AotConverterRegistry.Register(new AotConverter_37()); + AotConverterRegistry.Register(new AotConverter_38()); + AotConverterRegistry.Register(new AotConverter_39()); + AotConverterRegistry.Register(new AotConverter_40()); + AotConverterRegistry.Register(new AotConverter_41()); + AotConverterRegistry.Register(new AotConverter_42()); + AotConverterRegistry.Register(new AotConverter_43()); + AotConverterRegistry.Register(new AotConverter_44()); + AotConverterRegistry.Register(new AotConverter_45()); + AotConverterRegistry.Register(new AotConverter_46()); + AotConverterRegistry.Register(new AotConverter_47()); + AotConverterRegistry.Register(new AotConverter_48()); + AotConverterRegistry.Register(new AotConverter_49()); + AotConverterRegistry.Register(new AotConverter_50()); + AotConverterRegistry.Register(new AotConverter_51()); + AotConverterRegistry.Register(new AotConverter_52()); + AotConverterRegistry.Register(new AotConverter_53()); + AotConverterRegistry.Register(new AotConverter_54()); + AotConverterRegistry.Register(new AotConverter_55()); + AotConverterRegistry.Register(new AotConverter_56()); + AotConverterRegistry.Register(new AotConverter_57()); + AotConverterRegistry.Register(new AotConverter_58()); + AotConverterRegistry.Register(new AotConverter_59()); + AotConverterRegistry.Register(new AotConverter_60()); + AotConverterRegistry.Register(new AotConverter_61()); + AotConverterRegistry.Register(new AotConverter_62()); + AotConverterRegistry.Register(new AotConverter_63()); + AotConverterRegistry.Register(new AotConverter_64()); + AotConverterRegistry.Register(new AotConverter_65()); + } +} diff --git a/TUnit.Core.SourceGenerator.Tests/AotConverterGeneratorTests.cs b/TUnit.Core.SourceGenerator.Tests/AotConverterGeneratorTests.cs new file mode 100644 index 0000000000..3c1095c104 --- /dev/null +++ b/TUnit.Core.SourceGenerator.Tests/AotConverterGeneratorTests.cs @@ -0,0 +1,19 @@ +using TUnit.Core.SourceGenerator.Tests.Options; + +namespace TUnit.Core.SourceGenerator.Tests; + +public class AotConverterGeneratorTests : TestsBase +{ + [Test] + [Skip("Need to investigate - Behaves differently on local vs CI")] + public Task GeneratesCode() => AotConverterGenerator.RunTest( + Path.GetTempFileName(), + new RunTestOptions + { + AdditionalFiles = Sourcy.DotNet.Projects.TUnit_TestProject.Directory!.EnumerateFiles("*.cs", SearchOption.AllDirectories).Select(x => x.FullName).ToArray() + }, + async generatedFiles => + { + await Assert.That(generatedFiles.Length).IsGreaterThan(0); + }); +} diff --git a/TUnit.Core.SourceGenerator.Tests/TestsBase.cs b/TUnit.Core.SourceGenerator.Tests/TestsBase.cs index 468d28f72c..161a01beed 100644 --- a/TUnit.Core.SourceGenerator.Tests/TestsBase.cs +++ b/TUnit.Core.SourceGenerator.Tests/TestsBase.cs @@ -16,6 +16,7 @@ protected TestsBase() } public TestsBase TestMetadataGenerator = new(); + public TestsBase AotConverterGenerator = new(); public TestsBase HooksGenerator = new(); public TestsBase AssemblyLoaderGenerator = new(); public TestsBase DisableReflectionScannerGenerator = new(); diff --git a/TUnit.Core.SourceGenerator/CodeGenerators/Formatting/TypedConstantFormatter.cs b/TUnit.Core.SourceGenerator/CodeGenerators/Formatting/TypedConstantFormatter.cs index 02acfca105..f02c9c614a 100644 --- a/TUnit.Core.SourceGenerator/CodeGenerators/Formatting/TypedConstantFormatter.cs +++ b/TUnit.Core.SourceGenerator/CodeGenerators/Formatting/TypedConstantFormatter.cs @@ -390,7 +390,7 @@ private static string EscapeForTestId(string str) var needsEscape = false; foreach (var c in str) { - if (c == '\\' || c == '\r' || c == '\n' || c == '\t' || c == '"') + if (c is '\\' or '\r' or '\n' or '\t' or '"') { needsEscape = true; break; diff --git a/TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs b/TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs index e7f8fe57f8..c3ff931fde 100644 --- a/TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs @@ -1,12 +1,8 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using TUnit.Core.SourceGenerator.CodeGenerators; using TUnit.Core.SourceGenerator.Extensions; namespace TUnit.Core.SourceGenerator.Generators; @@ -17,293 +13,419 @@ public class AotConverterGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { var allTypes = context.CompilationProvider - .Select((compilation, _) => + .Select((compilation, ct) => { - var conversionInfos = new List(); + try + { + var conversionInfos = new List(); + ScanTestParameters(compilation, conversionInfos, ct); + return conversionInfos.ToImmutableArray(); + } + catch (NullReferenceException ex) + { + var stackTrace = ex.StackTrace ?? "No stack trace"; + throw new InvalidOperationException($"NullReferenceException in ScanTestParameters: {ex.Message}\nStack: {stackTrace}", ex); + } + }); - // 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); + context.RegisterSourceOutput(allTypes, (spc, source) => + { + try + { + GenerateConverters(spc, source); + } + catch (Exception e) + { + spc.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor( + id: "TUNITGEN001", + title: "TUnit.AotConverterGenerator Failed", + messageFormat: "Generator failed with exception: {0}", + category: "TUnit.Generator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: e.ToString()), + Location.None)); + } + }); + } - // Scan referenced assemblies for conversion operators - ScanReferencedAssemblies(compilation, conversionInfos); + private void ScanTestParameters(Compilation compilation, List conversionInfos, CancellationToken cancellationToken) + { + var typesToScan = new HashSet(SymbolEqualityComparer.Default); - // Scan method parameters for closed generic types like OneOf - ScanClosedGenericTypesInParameters(compilation, conversionInfos); + foreach (var tree in compilation.SyntaxTrees) + { + cancellationToken.ThrowIfCancellationRequested(); - return conversionInfos.ToImmutableArray(); - }); + var semanticModel = compilation.GetSemanticModel(tree); + var root = tree.GetRoot(); + + var methods = root.DescendantNodes() + .OfType(); + + foreach (var method in methods) + { + var methodSymbol = semanticModel.GetDeclaredSymbol(method); + if (methodSymbol == null) + { + continue; + } + + if (!IsTestMethod(methodSymbol)) + { + continue; + } + + foreach (var parameter in methodSymbol.Parameters) + { + typesToScan.Add(parameter.Type); + ScanAttributesForTypes(parameter.GetAttributes(), typesToScan); + } + + ScanAttributesForTypes(methodSymbol.GetAttributes(), typesToScan); + } + + var classes = root.DescendantNodes() + .OfType(); - context.RegisterSourceOutput(allTypes, GenerateConverters!); + foreach (var classDecl in classes) + { + var classSymbol = semanticModel.GetDeclaredSymbol(classDecl); + if (classSymbol == null) + { + continue; + } + + if (!IsTestClass(classSymbol)) + { + continue; + } + + ScanAttributesForTypes(classSymbol.GetAttributes(), typesToScan); + + foreach (var constructor in classSymbol.Constructors) + { + if (constructor.IsImplicitlyDeclared) + { + continue; + } + + foreach (var parameter in constructor.Parameters) + { + typesToScan.Add(parameter.Type); + ScanAttributesForTypes(parameter.GetAttributes(), typesToScan); + } + } + } + } + + foreach (var type in typesToScan) + { + cancellationToken.ThrowIfCancellationRequested(); + CollectConversionsForType(type, conversionInfos, compilation); + } } - private void ScanAllTypesInCompilation(Compilation compilation, List conversionInfos) + private bool IsTestMethod(IMethodSymbol method) { - // 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); + return method.GetAttributes().Any(attr => + { + var attrClass = attr.AttributeClass; + if (attrClass == null) + { + return false; + } - // Scan from the assembly's global namespace - this includes all types in the compilation - CollectTypesFromNamespace(compilation.Assembly.GlobalNamespace, compilationTypes); + var baseType = attrClass; + while (baseType != null) + { + if (baseType.ToDisplayString() == WellKnownFullyQualifiedClassNames.BaseTestAttribute.WithoutGlobalPrefix) + { + return true; + } + baseType = baseType.BaseType; + } + + return false; + }); + } + + private bool IsTestClass(INamedTypeSymbol classSymbol) + { + return classSymbol.GetMembers() + .OfType() + .Any(IsTestMethod); + } - // For each type in the compilation, check if it has conversion operators - foreach (var type in compilationTypes) + private void ScanAttributesForTypes(ImmutableArray attributes, HashSet typesToScan) + { + foreach (var attribute in attributes) { - // Only process public types - if (type.DeclaredAccessibility != Accessibility.Public) + if (attribute.AttributeClass == null) + { + continue; + } + + if (!IsDataSourceAttribute(attribute.AttributeClass)) { 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) + if (attribute.AttributeClass.IsGenericType) { - // Generate implicit converters from each type argument to the OneOf type - foreach (var typeArg in oneOfTypeArguments) + foreach (var typeArg in attribute.AttributeClass.TypeArguments) { - // 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); + typesToScan.Add(typeArg); } } - // 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 arg in attribute.ConstructorArguments) + { + ScanTypedConstantForTypes(arg, typesToScan); + } - foreach (var method in conversionOperators) + foreach (var namedArg in attribute.NamedArguments) { - var conversionInfo = GetConversionInfoFromSymbol(method); - if (conversionInfo != null) - { - conversionInfos.Add(conversionInfo); - } + ScanTypedConstantForTypes(namedArg.Value, typesToScan); } } } - private bool InheritsFromOneOfBase(INamedTypeSymbol type, out ImmutableArray? typeArguments) + private bool IsDataSourceAttribute(INamedTypeSymbol attributeClass) { - typeArguments = null; - - var currentType = type.BaseType; + var currentType = attributeClass; while (currentType != null) { - // Check if this is OneOfBase - if (currentType.Name == "OneOfBase" && currentType.ContainingNamespace?.ToDisplayString() == "OneOf") + var fullName = currentType.ToDisplayString(); + if (fullName == WellKnownFullyQualifiedClassNames.AsyncDataSourceGeneratorAttribute.WithoutGlobalPrefix || + fullName == WellKnownFullyQualifiedClassNames.AsyncUntypedDataSourceGeneratorAttribute.WithoutGlobalPrefix || + fullName == WellKnownFullyQualifiedClassNames.ArgumentsAttribute.WithoutGlobalPrefix) + { + return true; + } + + if (currentType.AllInterfaces.Any(i => + i.ToDisplayString() == WellKnownFullyQualifiedClassNames.IDataSourceAttribute.WithoutGlobalPrefix)) { - typeArguments = currentType.TypeArguments; return true; } + currentType = currentType.BaseType; } return false; } - private void ScanClosedGenericTypesInParameters(Compilation compilation, List conversionInfos) + private void ScanTypedConstantForTypes(TypedConstant constant, HashSet typesToScan) { - // Find all closed generic types used in method parameters - var closedGenericTypesInUse = new HashSet(SymbolEqualityComparer.Default); - - foreach (var tree in compilation.SyntaxTrees) + if (constant.IsNull) { - var semanticModel = compilation.GetSemanticModel(tree); - var root = tree.GetRoot(); + return; + } - // Find all method declarations - var methods = root.DescendantNodes() - .OfType(); + if (constant is { Kind: TypedConstantKind.Type, Value: ITypeSymbol typeValue }) + { + typesToScan.Add(typeValue); + } - foreach (var method in methods) + else if (constant is { Kind: TypedConstantKind.Array, IsNull: false }) + { + foreach (var element in constant.Values) { - var methodSymbol = semanticModel.GetDeclaredSymbol(method); - if (methodSymbol == null) - { - continue; - } - - // Collect parameter types - foreach (var parameter in methodSymbol.Parameters) - { - CollectClosedGenericTypes(parameter.Type, closedGenericTypesInUse); - } + ScanTypedConstantForTypes(element, typesToScan); } } + else if (constant.Kind != TypedConstantKind.Array && constant is { Value: not null, Type: not null }) + { + typesToScan.Add(constant.Type); + } + } - // For each closed generic type, check if it has conversion operators - foreach (var type in closedGenericTypesInUse) + private void CollectConversionsForType(ITypeSymbol type, List conversionInfos, Compilation compilation) + { + if (type is not INamedTypeSymbol namedType) { - // 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); + return; + } - foreach (var method in conversionOperators) + if (!ShouldIncludeType(namedType, compilation)) + { + return; + } + + var conversionOperators = namedType.GetMembers() + .OfType() + .Where(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && + m is { IsStatic: true, Parameters.Length: 1 }); + + foreach (var method in conversionOperators) + { + var conversionInfo = GetConversionInfoFromSymbol(method, compilation); + if (conversionInfo != null) { - var conversionInfo = GetConversionInfoFromSymbol(method); - if (conversionInfo != null) - { - conversionInfos.Add(conversionInfo); - } + conversionInfos.Add(conversionInfo); } } - } - private void CollectClosedGenericTypes(ITypeSymbol type, HashSet types) - { - if (type is INamedTypeSymbol { IsGenericType: true } namedType && !namedType.IsUnboundGenericType) + if (namedType.IsGenericType) { - types.Add(namedType); - - // Recursively collect type arguments foreach (var typeArg in namedType.TypeArguments) { - CollectClosedGenericTypes(typeArg, types); + CollectConversionsForType(typeArg, conversionInfos, compilation); } } + } - // Handle arrays - if (type is IArrayTypeSymbol arrayType) + private bool ShouldIncludeType(INamedTypeSymbol type, Compilation compilation) + { + var typeAssembly = type.ContainingAssembly; + var currentAssembly = compilation.Assembly; + + if (currentAssembly == null) + { + return false; + } + + if (SymbolEqualityComparer.Default.Equals(typeAssembly, currentAssembly)) + { + return true; + } + + if (type.DeclaredAccessibility == Accessibility.Public) { - CollectClosedGenericTypes(arrayType.ElementType, types); + return true; } + + if (type.DeclaredAccessibility == Accessibility.Internal) + { + if (typeAssembly != null && typeAssembly.GivesAccessTo(currentAssembly)) + { + return true; + } + } + + return false; } - private void ScanReferencedAssemblies(Compilation compilation, List conversionInfos) + private bool IsAccessibleType(ITypeSymbol type, Compilation compilation) { - // Get all types from referenced assemblies - var referencedTypes = new HashSet(SymbolEqualityComparer.Default); + if (type == null || compilation == null) + { + return false; + } - foreach (var reference in compilation.References) + if (type.SpecialType != SpecialType.None) { - 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; - } + return true; + } - CollectTypesFromNamespace(assemblySymbol.GlobalNamespace, referencedTypes); - } + if (type.TypeKind == TypeKind.TypeParameter) + { + return true; } - // Find conversion operators in referenced types - foreach (var type in referencedTypes) + if (type is INamedTypeSymbol namedType) { - // Only process public types - if (type.DeclaredAccessibility != Accessibility.Public) + var typeAssembly = namedType.ContainingAssembly; + var currentAssembly = compilation.Assembly; + + if (currentAssembly != null && SymbolEqualityComparer.Default.Equals(typeAssembly, currentAssembly)) { - continue; + return true; } - // 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); + if (namedType.DeclaredAccessibility == Accessibility.Public) + { + return true; + } - foreach (var method in conversionOperators) + if (namedType.DeclaredAccessibility == Accessibility.Internal) { - var conversionInfo = GetConversionInfoFromSymbol(method); - if (conversionInfo != null) + if (currentAssembly == null) { - conversionInfos.Add(conversionInfo); + return false; } + + if (typeAssembly != null && typeAssembly.GivesAccessTo(currentAssembly)) + { + return true; + } + + return false; } - } - } - private void CollectTypesFromNamespace(INamespaceSymbol namespaceSymbol, HashSet types) - { - foreach (var member in namespaceSymbol.GetMembers()) - { - if (member is INamedTypeSymbol type) + if (namedType.IsGenericType) { - types.Add(type); - - // Recursively collect nested types - CollectNestedTypes(type, types); + foreach (var typeArg in namedType.TypeArguments) + { + if (!IsAccessibleType(typeArg, compilation)) + { + return false; + } + } } - else if (member is INamespaceSymbol childNamespace) + + if (namedType.ContainingType != null) { - CollectTypesFromNamespace(childNamespace, types); + return IsAccessibleType(namedType.ContainingType, compilation); } + + return false; } - } - private void CollectNestedTypes(INamedTypeSymbol type, HashSet types) - { - foreach (var nestedType in type.GetTypeMembers()) + if (type is IArrayTypeSymbol arrayType) + { + return IsAccessibleType(arrayType.ElementType, compilation); + } + + if (type is IPointerTypeSymbol pointerType) { - types.Add(nestedType); - CollectNestedTypes(nestedType, types); + return IsAccessibleType(pointerType.PointedAtType, compilation); } + + return false; } - private ConversionInfo? GetConversionInfoFromSymbol(IMethodSymbol methodSymbol) + private ConversionInfo? GetConversionInfoFromSymbol(IMethodSymbol methodSymbol, Compilation compilation) { var containingType = methodSymbol.ContainingType; + if (containingType == null) + { + return null; + } + 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 if (sourceType.IsGenericDefinition() || targetType.IsGenericDefinition()) { return null; } - // Skip ref structs (Span, ReadOnlySpan, etc.) - they cannot be boxed to object - if (sourceType.IsRefLikeType || targetType.IsRefLikeType) + if (TypeContainsGenericTypeParameters(sourceType) || TypeContainsGenericTypeParameters(targetType)) { 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) + if (sourceType.IsRefLikeType || targetType.IsRefLikeType) { 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) + if (sourceType.TypeKind == TypeKind.Pointer || targetType.TypeKind == TypeKind.Pointer || + sourceType.SpecialType == SpecialType.System_Void || targetType.SpecialType == SpecialType.System_Void) { return null; } - // 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) + if (!IsAccessibleType(containingType, compilation)) { return null; } - if (targetType is INamedTypeSymbol { SpecialType: SpecialType.None } namedTargetType && - namedTargetType.DeclaredAccessibility != Accessibility.Public) + if (!IsAccessibleType(sourceType, compilation) || !IsAccessibleType(targetType, compilation)) { return null; } @@ -318,6 +440,37 @@ private void CollectNestedTypes(INamedTypeSymbol type, HashSet }; } + private bool TypeContainsGenericTypeParameters(ITypeSymbol type) + { + if (type.TypeKind == TypeKind.TypeParameter) + { + return true; + } + + if (type is INamedTypeSymbol namedTypeSymbol) + { + foreach (var typeArgument in namedTypeSymbol.TypeArguments) + { + if (TypeContainsGenericTypeParameters(typeArgument)) + { + return true; + } + } + } + + if (type is IArrayTypeSymbol arrayTypeSymbol) + { + return TypeContainsGenericTypeParameters(arrayTypeSymbol.ElementType); + } + + if (type is IPointerTypeSymbol pointerTypeSymbol) + { + return TypeContainsGenericTypeParameters(pointerTypeSymbol.PointedAtType); + } + + return false; + } + private void GenerateConverters(SourceProductionContext context, ImmutableArray conversions) { var writer = new CodeWriter(); @@ -362,73 +515,138 @@ private void GenerateConverters(SourceProductionContext context, ImmutableArray< foreach (var conversion in uniqueConversions) { + try + { + if (conversion.SourceType == null || conversion.TargetType == null) + { + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor( + id: "TUNITGEN002", + title: "Null type in conversion", + messageFormat: "Skipping converter generation: SourceType={0}, TargetType={1}", + category: "TUnit.Generator", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true), + Location.None, + conversion.SourceType?.ToDisplayString() ?? "null", + conversion.TargetType?.ToDisplayString() ?? "null")); + continue; + } + } + catch (Exception ex) + { + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor( + id: "TUNITGEN003", + title: "Error checking conversion types", + messageFormat: "Error during null check: {0}", + category: "TUnit.Generator", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true), + Location.None, + ex.ToString())); + continue; + } + var converterClassName = $"AotConverter_{converterIndex++}"; var sourceTypeName = conversion.SourceType.GloballyQualified(); var targetTypeName = conversion.TargetType.GloballyQualified(); - + writer.AppendLine($"internal sealed class {converterClassName} : IAotConverter"); writer.AppendLine("{"); writer.Indent(); - + writer.AppendLine($"public Type SourceType => typeof({sourceTypeName});"); writer.AppendLine($"public Type TargetType => typeof({targetTypeName});"); writer.AppendLine(); - + writer.AppendLine("public object? Convert(object? value)"); writer.AppendLine("{"); writer.Indent(); writer.AppendLine("if (value == null) return null;"); - // 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 + // Use Zen's more robust approach for handling nullable types and type checks 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 targetType = conversion.TargetType; - var patternTypeName = underlyingType.GloballyQualified(); + ITypeSymbol typeForTargetPattern = targetType; + if (targetType is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T, TypeArguments.Length: > 0 } nullableTargetType) + { + typeForTargetPattern = nullableTargetType.TypeArguments[0]; + } + var targetPatternTypeName = typeForTargetPattern.GloballyQualified(); - writer.AppendLine($"if (value is {patternTypeName} typedValue)"); + writer.AppendLine($"if (value is {targetPatternTypeName} targetTypedValue)"); 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.AppendLine("return targetTypedValue;"); writer.Unindent(); writer.AppendLine("}"); + + // 2. If types are different, generate the fallback check for the source type. + // This handles cases that require an implicit conversion. + if (!SymbolEqualityComparer.Default.Equals(sourceType, targetType)) + { + // For pattern matching, we must unwrap nullable types (C# language requirement - CS8116) + ITypeSymbol typeForSourcePattern = sourceType; + if (sourceType is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T, TypeArguments.Length: > 0 } nullableSourceType) + { + typeForSourcePattern = nullableSourceType.TypeArguments[0]; + } + + var sourcePatternTypeName = typeForSourcePattern.GloballyQualified(); + + writer.AppendLine(); + writer.AppendLine($"if (value is {sourcePatternTypeName} sourceTypedValue)"); + writer.AppendLine("{"); + writer.Indent(); + // For explicit conversions, we need to use an explicit cast + // For implicit conversions, variable assignment works fine + if (conversion.IsImplicit) + { + writer.AppendLine($"{targetTypeName} converted = sourceTypedValue;"); + } + else + { + writer.AppendLine($"{targetTypeName} converted = ({targetTypeName})sourceTypedValue;"); + } + writer.AppendLine("return converted;"); + writer.Unindent(); + writer.AppendLine("}"); + } + writer.AppendLine("return value; // Return original value if type doesn't match"); - + writer.Unindent(); writer.AppendLine("}"); - + writer.Unindent(); writer.AppendLine("}"); writer.AppendLine(); - + registrations.Add($"AotConverterRegistry.Register(new {converterClassName}());"); } - + writer.AppendLine("internal static class AotConverterRegistration"); writer.AppendLine("{"); writer.Indent(); - + writer.AppendLine("[global::System.Runtime.CompilerServices.ModuleInitializer]"); writer.AppendLine("[global::System.Diagnostics.CodeAnalysis.SuppressMessage(\"Performance\", \"CA2255:The 'ModuleInitializer' attribute should not be used in libraries\","); writer.AppendLine(" Justification = \"Test framework needs to register AOT converters for conversion operators\")]"); writer.AppendLine("public static void Initialize()"); writer.AppendLine("{"); writer.Indent(); - + foreach (var registration in registrations) { writer.AppendLine(registration); } - + writer.Unindent(); writer.AppendLine("}"); - + writer.Unindent(); writer.AppendLine("}"); @@ -463,4 +681,4 @@ public int GetHashCode((ITypeSymbol Source, ITypeSymbol Target) obj) } } } -} \ No newline at end of file +} diff --git a/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs b/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs index ae72be61cf..43a2a55606 100644 --- a/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs @@ -3681,7 +3681,7 @@ private static void MapGenericTypeArguments(ITypeSymbol paramType, ITypeSymbol a var baseTypeNamespace = baseTypeDef.ContainingNamespace?.ToDisplayString(); // Check for typed data source base classes more precisely - if ((baseTypeName == "DataSourceGeneratorAttribute" || baseTypeName == "AsyncDataSourceGeneratorAttribute") && + if (baseTypeName is "DataSourceGeneratorAttribute" or "AsyncDataSourceGeneratorAttribute" && baseTypeNamespace?.Contains("TUnit.Core") == true) { // Get the type arguments from the base class @@ -4580,7 +4580,7 @@ private static bool AreSameAttribute(AttributeData a1, AttributeData a2) var namespaceName = current.ContainingNamespace?.ToDisplayString(); // Check for exact match of the typed base classes - if ((name == "DataSourceGeneratorAttribute" || name == "AsyncDataSourceGeneratorAttribute") && + if (name is "DataSourceGeneratorAttribute" or "AsyncDataSourceGeneratorAttribute" && namespaceName?.Contains("TUnit.Core") == true) { return current; diff --git a/TUnit.Core/Converters/AotConverterRegistry.cs b/TUnit.Core/Converters/AotConverterRegistry.cs index 6d7c08b553..22bd65354b 100644 --- a/TUnit.Core/Converters/AotConverterRegistry.cs +++ b/TUnit.Core/Converters/AotConverterRegistry.cs @@ -8,7 +8,7 @@ namespace TUnit.Core.Converters; public static class AotConverterRegistry { private static readonly ConcurrentDictionary<(Type Source, Type Target), IAotConverter> Converters = new(); - + /// /// Registers a converter /// @@ -16,7 +16,15 @@ public static void Register(IAotConverter converter) { Converters.TryAdd((converter.SourceType, converter.TargetType), converter); } - + + /// + /// Registers a converter + /// + public static void Register(Func converter) + { + Converters.TryAdd((typeof(TSource), typeof(TTarget)), new FuncAotConverter(converter)); + } + /// /// Tries to get a converter for the specified types /// @@ -24,7 +32,7 @@ public static bool TryGetConverter(Type sourceType, Type targetType, out IAotCon { return Converters.TryGetValue((sourceType, targetType), out converter); } - + /// /// Tries to convert a value using a registered converter /// @@ -35,8 +43,8 @@ public static bool TryConvert(Type sourceType, Type targetType, object? value, o result = converter.Convert(value); return true; } - + result = null; return false; } -} \ No newline at end of file +} diff --git a/TUnit.Core/Converters/FuncAotConverter.cs b/TUnit.Core/Converters/FuncAotConverter.cs new file mode 100644 index 0000000000..43e9bf417d --- /dev/null +++ b/TUnit.Core/Converters/FuncAotConverter.cs @@ -0,0 +1,8 @@ +namespace TUnit.Core.Converters; + +public class FuncAotConverter(Func converter) : IAotConverter +{ + public Type SourceType { get; } = typeof(TSource); + public Type TargetType { get; } = typeof(TTarget); + public object? Convert(object? value) => converter((TSource)value!); +} diff --git a/TUnit.Core/Helpers/CastHelper.cs b/TUnit.Core/Helpers/CastHelper.cs index 7edeaa9779..928f7eed86 100644 --- a/TUnit.Core/Helpers/CastHelper.cs +++ b/TUnit.Core/Helpers/CastHelper.cs @@ -23,7 +23,8 @@ public static class CastHelper return t; } - return (T?)Cast(typeof(T), value); + var result = Cast(typeof(T), value); + return (T?)result; } /// @@ -71,11 +72,25 @@ public static class CastHelper return result; } + // In AOT mode, if we reach here, throw a helpful diagnostic error + if (IsAotMode()) + { + throw new InvalidCastException( + $"Cannot convert from '{sourceType.FullName}' to '{targetType.FullName}' in Native AOT mode. " + + $"No AOT converter was found in the AotConverterRegistry. " + + $"This typically happens when:\n" + + $"1. A conversion operator exists but was not discovered during source generation (e.g., operators generated by other source generators like OneOf)\n" + + $"2. The conversion requires reflection which is not supported in AOT\n" + + $"Consider:\n" + + $"- Manually registering an AOT converter using AotConverterRegistry.Register()\n" + + $"- Using explicit type conversions instead of relying on implicit operators\n" + + $"- Checking if your conversion operator is being generated after TUnit's source generators run"); + } + // 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 @@ -279,7 +294,6 @@ private static bool TryConvertArray(Type targetType, Type sourceType, object val 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) { @@ -333,14 +347,12 @@ private static bool TryConvertArray(Type targetType, Type sourceType, object val return null; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool HasCorrectInputType(Type expectedType, MethodInfo method) { var parameters = method.GetParameters(); return parameters.Length == 1 && parameters[0].ParameterType == expectedType; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsAotMode() { #if NET diff --git a/TUnit.Engine.Tests/TUnit.Engine.Tests.csproj b/TUnit.Engine.Tests/TUnit.Engine.Tests.csproj index a283e6079d..4349b07740 100644 --- a/TUnit.Engine.Tests/TUnit.Engine.Tests.csproj +++ b/TUnit.Engine.Tests/TUnit.Engine.Tests.csproj @@ -12,8 +12,14 @@ - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/TUnit.Pipeline/TUnit.Pipeline.csproj b/TUnit.Pipeline/TUnit.Pipeline.csproj index 959473ebe0..396086de73 100644 --- a/TUnit.Pipeline/TUnit.Pipeline.csproj +++ b/TUnit.Pipeline/TUnit.Pipeline.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0 false false @@ -14,8 +14,14 @@ - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + 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 8186499f2e..507465e7da 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 @@ -1610,9 +1610,17 @@ namespace .Converters public static class AotConverterRegistry { public static void Register(. converter) { } + public static void Register( converter) { } public static bool TryConvert( sourceType, targetType, object? value, out object? result) { } public static bool TryGetConverter( sourceType, targetType, out .? converter) { } } + public class FuncAotConverter : . + { + public FuncAotConverter( converter) { } + public SourceType { get; } + public TargetType { get; } + public object? Convert(object? value) { } + } public interface IAotConverter { SourceType { get; } 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 0e54ceb230..6a4c6c468a 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 @@ -1610,9 +1610,17 @@ namespace .Converters public static class AotConverterRegistry { public static void Register(. converter) { } + public static void Register( converter) { } public static bool TryConvert( sourceType, targetType, object? value, out object? result) { } public static bool TryGetConverter( sourceType, targetType, out .? converter) { } } + public class FuncAotConverter : . + { + public FuncAotConverter( converter) { } + public SourceType { get; } + public TargetType { get; } + public object? Convert(object? value) { } + } public interface IAotConverter { SourceType { get; } 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 68477ecf97..1121753df0 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 @@ -1610,9 +1610,17 @@ namespace .Converters public static class AotConverterRegistry { public static void Register(. converter) { } + public static void Register( converter) { } public static bool TryConvert( sourceType, targetType, object? value, out object? result) { } public static bool TryGetConverter( sourceType, targetType, out .? converter) { } } + public class FuncAotConverter : . + { + public FuncAotConverter( converter) { } + public SourceType { get; } + public TargetType { get; } + public object? Convert(object? value) { } + } public interface IAotConverter { SourceType { get; } 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 401d09948e..7975397fa4 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 @@ -1562,9 +1562,17 @@ namespace .Converters public static class AotConverterRegistry { public static void Register(. converter) { } + public static void Register( converter) { } public static bool TryConvert( sourceType, targetType, object? value, out object? result) { } public static bool TryGetConverter( sourceType, targetType, out .? converter) { } } + public class FuncAotConverter : . + { + public FuncAotConverter( converter) { } + public SourceType { get; } + public TargetType { get; } + public object? Convert(object? value) { } + } public interface IAotConverter { SourceType { get; } diff --git a/TUnit.RpcTests/TUnit.RpcTests.csproj b/TUnit.RpcTests/TUnit.RpcTests.csproj index fea0c05a2e..d15c516411 100644 --- a/TUnit.RpcTests/TUnit.RpcTests.csproj +++ b/TUnit.RpcTests/TUnit.RpcTests.csproj @@ -1,7 +1,7 @@ - + - net8.0 + net10.0 enable enable diff --git a/TUnit.TestProject/GlobalSetup.cs b/TUnit.TestProject/GlobalSetup.cs new file mode 100644 index 0000000000..14916fb853 --- /dev/null +++ b/TUnit.TestProject/GlobalSetup.cs @@ -0,0 +1,18 @@ +using TUnit.Core.Converters; + +namespace TUnit.TestProject; + +public class GlobalSetup +{ + [Before(TestDiscovery)] + public static void SetupAotConverters() + { + AotConverterRegistry.Register(value => new MixedMatrixTestsUnion1(value)); + AotConverterRegistry.Register(value => new MixedMatrixTestsUnion1(value)); + AotConverterRegistry.Register(value => new MixedMatrixTestsUnion1(value)); + + AotConverterRegistry.Register(value => new MixedMatrixTestsUnion2(value)); + AotConverterRegistry.Register(value => new MixedMatrixTestsUnion2(value)); + AotConverterRegistry.Register(value => new MixedMatrixTestsUnion2(value)); + } +}