Skip to content

Commit e2fbc2f

Browse files
AnthonyLloydCopilot
andcommitted
feat: AOT compatibility - move collection properties to extension methods
Move Array, Array2D, List, HashSet, and ArrayUnique from Gen<T> instance properties to extension methods on the static Gen class. This prevents infinite generic type expansion during NativeAOT compilation (ILC) when Gen<T> is instantiated for self-referential types. BREAKING CHANGE: .Array, .Array2D, .List, .HashSet, .ArrayUnique are now methods requiring parentheses: .Array(), .Array2D(), .List(), .HashSet(), .ArrayUnique(). Indexer syntax is unchanged: .Array()[0, 5]. - Add IsAotCompatible to CsCheck.csproj - Update all call sites in library, tests, and documentation - Verified: 299 tests pass, NativeAOT compiles including recursive types Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0ca2947 commit e2fbc2f

22 files changed

Lines changed: 178 additions & 188 deletions

CsCheck/Check.cs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2026 Anthony Lloyd
1+
// Copyright 2026 Anthony Lloyd
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -40,7 +40,6 @@ public static partial class Check
4040
public static int Ulps = ParseEnvironmentVariableToInt("CsCheck_Ulps", 4);
4141
/// <summary>The number of Where Gne iterations before throwing an exception.</summary>
4242
public static int WhereLimit = ParseEnvironmentVariableToInt("CsCheck_WhereLimit", 100);
43-
internal static bool IsDebug = Assembly.GetCallingAssembly().GetCustomAttribute<DebuggableAttribute>()?.IsJITTrackingEnabled ?? false;
4443

4544
sealed class SampleActionWorker<T>(Gen<T> gen, Action<T> assert, CountdownEvent cde, string? seed, long target, bool isIter) : IThreadPoolWorkItem
4645
{
@@ -1408,7 +1407,7 @@ public static void SampleModelBased<Actual, Model>(this Gen<(Actual, Model)> ini
14081407
}
14091408

14101409
new GenInitial<Actual, Model>(initial)
1411-
.Select(Gen.OneOf(opNameActions).Array, (a, b) => new ModelBasedData<Actual, Model>(a.Actual, a.Model, a.Stream, a.Seed, b))
1410+
.Select(Gen.OneOf(opNameActions).Array(), (a, b) => new ModelBasedData<Actual, Model>(a.Actual, a.Model, a.Stream, a.Seed, b))
14121411
.Sample(d =>
14131412
{
14141413
try
@@ -1754,7 +1753,7 @@ public static void SampleParallel<T>(this Gen<T> initial, GenOperation<T>[] oper
17541753
Gen.Int[2, maxParallelOperations]
17551754
.SelectMany(np => Gen.Int[2, Math.Min(threads, np)].Select(nt => (nt, np)))
17561755
.SelectMany((nt, np) => Gen.Int[0, maxSequentialOperations].Select(ns => (ns, nt, np)))
1757-
.SelectMany((ns, nt, np) => new GenSampleParallel<T>(initial).Select(genOps.Array[ns], genOps.Array[np])
1756+
.SelectMany((ns, nt, np) => new GenSampleParallel<T>(initial).Select(genOps.Array()[ns], genOps.Array()[np])
17581757
.Select((initial, sequential, parallel) => (initial, sequential, nt, parallel)))
17591758
.Select((initial, sequential, threads, parallel) => new SampleParallelData<T>(initial.Value, initial.Stream, initial.Seed, sequential, parallel, threads))
17601759
.Sample(spd =>
@@ -2018,7 +2017,7 @@ public static void SampleParallel<Actual, Model>(this Gen<(Actual, Model)> initi
20182017
Gen.Int[2, maxParallelOperations]
20192018
.SelectMany(np => Gen.Int[2, Math.Min(threads, np)].Select(nt => (nt, np)))
20202019
.SelectMany((nt, np) => Gen.Int[0, maxSequentialOperations].Select(ns => (ns, nt, np)))
2021-
.SelectMany((ns, nt, np) => new GenSampleParallel<Actual, Model>(initial).Select(genOps.Array[ns], genOps.Array[np])
2020+
.SelectMany((ns, nt, np) => new GenSampleParallel<Actual, Model>(initial).Select(genOps.Array()[ns], genOps.Array()[np])
20222021
.Select((initial, sequential, parallel) => (initial, sequential, nt, parallel)))
20232022
.Select((initial, sequential, threads, parallel) => new SampleParallelData<Actual, Model>(initial.Actual, initial.Model, initial.Stream, initial.Seed, sequential, parallel, threads))
20242023
.Sample(spd =>
@@ -2268,7 +2267,7 @@ public void Execute()
22682267
{
22692268
if (result.Add(fasterTimer.Time(), slowerTimer.Time()))
22702269
{
2271-
if (raiseexception && result.NotFaster && !IsDebug)
2270+
if (raiseexception && result.NotFaster)
22722271
result.Exception ??= new CsCheckException(result.ToString());
22732272
running = false;
22742273
return;
@@ -2354,7 +2353,7 @@ public void Execute()
23542353
{
23552354
if (result.Add(fasterTimer.Time(out var fasterValue), slowerTimer.Time(out var slowerValue)))
23562355
{
2357-
if (raiseexception && result.NotFaster && !IsDebug)
2356+
if (raiseexception && result.NotFaster)
23582357
result.Exception ??= new CsCheckException(result.ToString());
23592358
running = false;
23602359
return;
@@ -2410,7 +2409,7 @@ public static void Faster<T>(Func<T> faster, Func<T> slower, Func<T, T, bool>? e
24102409
while (--threads > 0)
24112410
ThreadPool.UnsafeQueueUserWorkItem(worker, false);
24122411
worker.Execute();
2413-
if (raiseexception && result.NotFaster && !IsDebug)
2412+
if (raiseexception && result.NotFaster)
24142413
throw new CsCheckException(result.ToString());
24152414
if (result.Exception is not null) throw result.Exception;
24162415
if (writeLine is not null) result.Output(writeLine);
@@ -2441,7 +2440,7 @@ async Task Worker()
24412440
{
24422441
if (result.Add(await fasterTimer.Time().ConfigureAwait(false), await slowerTimer.Time().ConfigureAwait(false)))
24432442
{
2444-
if (raiseexception && result.NotFaster && !IsDebug)
2443+
if (raiseexception && result.NotFaster)
24452444
result.Exception ??= new CsCheckException(result.ToString());
24462445
running = false;
24472446
return;
@@ -2498,7 +2497,7 @@ async Task Worker()
24982497
var (slowerTime, slowerValue) = await slowerTimer.Time().ConfigureAwait(false);
24992498
if (result.Add(fasterTime, slowerTime))
25002499
{
2501-
if (raiseexception && result.NotFaster && !IsDebug)
2500+
if (raiseexception && result.NotFaster)
25022501
result.Exception ??= new CsCheckException(result.ToString());
25032502
running = false;
25042503
return;
@@ -2552,7 +2551,7 @@ public void Execute()
25522551
t = gen.Generate(pcg, null, out _);
25532552
if (running && result.Add(fasterTimer.Time(t), slowerTimer.Time(t)))
25542553
{
2555-
if (raiseexception && result.NotFaster && !IsDebug)
2554+
if (raiseexception && result.NotFaster)
25562555
result.Exception ??= new CsCheckException(result.ToString());
25572556
running = false;
25582557
return;
@@ -2757,7 +2756,7 @@ async Task Worker()
27572756
if (!running) return;
27582757
if (result.Add(await fasterTimer.Time(t).ConfigureAwait(false), await slowerTimer.Time(t).ConfigureAwait(false)))
27592758
{
2760-
if (raiseexception && result.NotFaster && !IsDebug)
2759+
if (raiseexception && result.NotFaster)
27612760
result.Exception ??= new CsCheckException(result.ToString());
27622761
running = false;
27632762
return;
@@ -2918,7 +2917,7 @@ public void Execute()
29182917
t = gen.Generate(pcg, null, out _);
29192918
if (result.Add(fasterTimer.Time(t, out var fasterValue), slowerTimer.Time(t, out var slowerValue)))
29202919
{
2921-
if (raiseexception && result.NotFaster && !IsDebug)
2920+
if (raiseexception && result.NotFaster)
29222921
result.Exception ??= new CsCheckException(result.ToString());
29232922
running = false;
29242923
return;
@@ -3180,7 +3179,7 @@ async Task Worker()
31803179
var (slowerTime, slowerValue) = await slowerTimer.Time(t).ConfigureAwait(false);
31813180
if (result.Add(fasterTime, slowerTime))
31823181
{
3183-
if (raiseexception && result.NotFaster && !IsDebug)
3182+
if (raiseexception && result.NotFaster)
31843183
result.Exception ??= new CsCheckException(result.ToString());
31853184
running = false;
31863185
return;
@@ -3555,9 +3554,7 @@ public override string ToString()
35553554
var result = $"{Median.Median:P2}[{Median.Q1:P2}..{Median.Q3:P2}] {times:#0.00}x[{q1Times:#0.00}x..{q3Times:#0.00}x] {faster}";
35563555
if (double.IsNaN(Median.Median)) result = $"Time resolution too small try using repeat.\n{result}";
35573556
else if ((Median.Median >= 0.0) != (Faster > Slower)) result = $"Inconsistent result try using repeat or increasing sigma.\n{result}";
3558-
result = $"{result}, sigma = {Math.Sqrt(SigmaSquared):#0.0} ({Faster:#,0} vs {Slower:#,0}), min = {timeString((double)FasterMin / repeat)}{timeUnit} vs {timeString((double)SlowerMin / repeat)}{timeUnit}";
3559-
if (Check.IsDebug) result += " - DEBUG MODE - DO NOT TRUST THESE RESULTS";
3560-
return result;
3557+
return $"{result}, sigma = {Math.Sqrt(SigmaSquared):#0.0} ({Faster:#,0} vs {Slower:#,0}), min = {timeString((double)FasterMin / repeat)}{timeUnit} vs {timeString((double)SlowerMin / repeat)}{timeUnit}";
35613558
}
35623559

35633560
private static (Func<double, string>, string) TimeFormat(double maxValue) =>

CsCheck/CsCheck.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ Fixed Faster statistics locking.
4444
<NoWarn>CS1591,MA0143</NoWarn>
4545
<PackageReadmeFile>README.md</PackageReadmeFile>
4646
<AnalysisMode>All</AnalysisMode>
47+
<IsAotCompatible>true</IsAotCompatible>
4748
</PropertyGroup>
4849
<ItemGroup>
4950
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.103" PrivateAssets="All" />
50-
<PackageReference Include="Meziantou.Analyzer" Version="3.0.4" PrivateAssets="All" />
51+
<PackageReference Include="Meziantou.Analyzer" Version="3.0.20" PrivateAssets="All" />
5152
<None Include="../CsCheck.png" Pack="true" PackagePath="" Visible="False" />
5253
<None Include="../README.md" Pack="true" PackagePath="" Visible="False" />
5354
</ItemGroup>

CsCheck/Dbg.cs

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public static IEnumerable<string> Output()
9191
lock (stats)
9292
{
9393
var now = Stopwatch.GetTimestamp();
94-
return stats.Select(i =>
94+
return [.. stats.Select(i =>
9595
{
9696
var (completed, starts) = i.Value;
9797
lock (starts)
@@ -101,7 +101,7 @@ public static IEnumerable<string> Output()
101101
running.Add(now - start);
102102
return KeyValuePair.Create(i.Key, (completed, running));
103103
}
104-
}).ToArray();
104+
})];
105105
}
106106
}
107107

@@ -120,11 +120,8 @@ public static void Clear()
120120
objects = new();
121121
functions = new();
122122
times = new();
123-
if (regressionStream is not null)
124-
{
125-
regressionStream.Dispose();
126-
regressionStream = null;
127-
}
123+
regressionStream?.Dispose();
124+
regressionStream = null;
128125
}
129126

130127
/// <summary>Save object by name.</summary>
@@ -363,20 +360,14 @@ public IEnumerator<T> GetEnumerator()
363360
_cache.Add(current);
364361
yield return current;
365362
}
366-
if (_enumerator is not null)
367-
{
368-
_enumerator.Dispose();
369-
_enumerator = null;
370-
}
363+
_enumerator?.Dispose();
364+
_enumerator = null;
371365
for (; index < _cache.Count; index++) yield return _cache[index];
372366
}
373367
public void Dispose()
374368
{
375-
if (_enumerator is not null)
376-
{
377-
_enumerator.Dispose();
378-
_enumerator = null;
379-
}
369+
_enumerator?.Dispose();
370+
_enumerator = null;
380371
}
381372
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
382373
}
@@ -841,143 +832,143 @@ public static class RegressionExtensions
841832
public static void Add(this IRegression r, IEnumerable<bool> val)
842833
{
843834
if (val is null) { r.Add(NULL); return; }
844-
var col = val as ICollection<bool> ?? val.ToArray();
835+
var col = val as IReadOnlyCollection<bool> ?? [.. val];
845836
r.Add((uint)col.Count);
846837
foreach (var v in col) r.Add(v);
847838
}
848839

849840
public static void Add(this IRegression r, IEnumerable<byte> val)
850841
{
851842
if (val is null) { r.Add(NULL); return; }
852-
var col = val as ICollection<byte> ?? val.ToArray();
843+
var col = val as IReadOnlyCollection<byte> ?? [.. val];
853844
r.Add((uint)col.Count);
854845
foreach (var v in col) r.Add(v);
855846
}
856847

857848
public static void Add(this IRegression r, IEnumerable<char> val)
858849
{
859850
if (val is null) { r.Add(NULL); return; }
860-
var col = val as ICollection<char> ?? val.ToArray();
851+
var col = val as IReadOnlyCollection<char> ?? [.. val];
861852
r.Add((uint)col.Count);
862853
foreach (var v in col) r.Add(v);
863854
}
864855

865856
public static void Add(this IRegression r, IEnumerable<DateTime> val)
866857
{
867858
if (val is null) { r.Add(NULL); return; }
868-
var col = val as ICollection<DateTime> ?? val.ToArray();
859+
var col = val as IReadOnlyCollection<DateTime> ?? [.. val];
869860
r.Add((uint)col.Count);
870861
foreach (var v in col) r.Add(v);
871862
}
872863

873864
public static void Add(this IRegression r, IEnumerable<DateTimeOffset> val)
874865
{
875866
if (val is null) { r.Add(NULL); return; }
876-
var col = val as ICollection<DateTimeOffset> ?? val.ToArray();
867+
var col = val as IReadOnlyCollection<DateTimeOffset> ?? [.. val];
877868
r.Add((uint)col.Count);
878869
foreach (var v in col) r.Add(v);
879870
}
880871

881872
public static void Add(this IRegression r, IEnumerable<decimal> val)
882873
{
883874
if (val is null) { r.Add(NULL); return; }
884-
var col = val as ICollection<decimal> ?? val.ToArray();
875+
var col = val as IReadOnlyCollection<decimal> ?? [.. val];
885876
r.Add((uint)col.Count);
886877
foreach (var v in col) r.Add(v);
887878
}
888879

889880
public static void Add(this IRegression r, IEnumerable<double> val)
890881
{
891882
if (val is null) { r.Add(NULL); return; }
892-
var col = val as ICollection<double> ?? val.ToArray();
883+
var col = val as IReadOnlyCollection<double> ?? [.. val];
893884
r.Add((uint)col.Count);
894885
foreach (var v in col) r.Add(v);
895886
}
896887

897888
public static void Add(this IRegression r, IEnumerable<float> val)
898889
{
899890
if (val is null) { r.Add(NULL); return; }
900-
var col = val as ICollection<float> ?? val.ToArray();
891+
var col = val as IReadOnlyCollection<float> ?? [.. val];
901892
r.Add((uint)col.Count);
902893
foreach (var v in col) r.Add(v);
903894
}
904895

905896
public static void Add(this IRegression r, IEnumerable<Guid> val)
906897
{
907898
if (val is null) { r.Add(NULL); return; }
908-
var col = val as ICollection<Guid> ?? val.ToArray();
899+
var col = val as IReadOnlyCollection<Guid> ?? [.. val];
909900
r.Add((uint)col.Count);
910901
foreach (var v in col) r.Add(v);
911902
}
912903

913904
public static void Add(this IRegression r, IEnumerable<int> val)
914905
{
915906
if (val is null) { r.Add(NULL); return; }
916-
var col = val as ICollection<int> ?? val.ToArray();
907+
var col = val as IReadOnlyCollection<int> ?? [.. val];
917908
r.Add((uint)col.Count);
918909
foreach (var v in col) r.Add(v);
919910
}
920911

921912
public static void Add(this IRegression r, IEnumerable<long> val)
922913
{
923914
if (val is null) { r.Add(NULL); return; }
924-
var col = val as ICollection<long> ?? val.ToArray();
915+
var col = val as IReadOnlyCollection<long> ?? [.. val];
925916
r.Add((uint)col.Count);
926917
foreach (var v in col) r.Add(v);
927918
}
928919

929920
public static void Add(this IRegression r, IEnumerable<sbyte> val)
930921
{
931922
if (val is null) { r.Add(NULL); return; }
932-
var col = val as ICollection<sbyte> ?? val.ToArray();
923+
var col = val as IReadOnlyCollection<sbyte> ?? [.. val];
933924
r.Add((uint)col.Count);
934925
foreach (var v in col) r.Add(v);
935926
}
936927

937928
public static void Add(this IRegression r, IEnumerable<short> val)
938929
{
939930
if (val is null) { r.Add(NULL); return; }
940-
var col = val as ICollection<short> ?? val.ToArray();
931+
var col = val as IReadOnlyCollection<short> ?? [.. val];
941932
r.Add((uint)col.Count);
942933
foreach (var v in col) r.Add(v);
943934
}
944935

945936
public static void Add(this IRegression r, IEnumerable<string> val)
946937
{
947938
if (val is null) { r.Add(NULL); return; }
948-
var col = val as ICollection<string> ?? val.ToArray();
939+
var col = val as IReadOnlyCollection<string> ?? [.. val];
949940
r.Add((uint)col.Count);
950941
foreach (var v in col) r.Add(v);
951942
}
952943

953944
public static void Add(this IRegression r, IEnumerable<TimeSpan> val)
954945
{
955946
if (val is null) { r.Add(NULL); return; }
956-
var col = val as ICollection<TimeSpan> ?? val.ToArray();
947+
var col = val as IReadOnlyCollection<TimeSpan> ?? [.. val];
957948
r.Add((uint)col.Count);
958949
foreach (var v in col) r.Add(v);
959950
}
960951

961952
public static void Add(this IRegression r, IEnumerable<uint> val)
962953
{
963954
if (val is null) { r.Add(NULL); return; }
964-
var col = val as ICollection<uint> ?? val.ToArray();
955+
var col = val as IReadOnlyCollection<uint> ?? [.. val];
965956
r.Add((uint)col.Count);
966957
foreach (var v in col) r.Add(v);
967958
}
968959

969960
public static void Add(this IRegression r, IEnumerable<ulong> val)
970961
{
971962
if (val is null) { r.Add(NULL); return; }
972-
var col = val as ICollection<ulong> ?? val.ToArray();
963+
var col = val as IReadOnlyCollection<ulong> ?? [.. val];
973964
r.Add((uint)col.Count);
974965
foreach (var v in col) r.Add(v);
975966
}
976967

977968
public static void Add(this IRegression r, IEnumerable<ushort> val)
978969
{
979970
if (val is null) { r.Add(NULL); return; }
980-
var col = val as ICollection<ushort> ?? val.ToArray();
971+
var col = val as IReadOnlyCollection<ushort> ?? [.. val];
981972
r.Add((uint)col.Count);
982973
foreach (var v in col) r.Add(v);
983974
}

0 commit comments

Comments
 (0)