diff --git a/JustDummies.UnitTests/JustDummies.UnitTests.csproj b/JustDummies.UnitTests/JustDummies.UnitTests.csproj
index 51ba6fe0..81ca385d 100644
--- a/JustDummies.UnitTests/JustDummies.UnitTests.csproj
+++ b/JustDummies.UnitTests/JustDummies.UnitTests.csproj
@@ -47,6 +47,9 @@
+
+
diff --git a/JustDummies.UnitTests/NullArgumentGuardConventionTests.cs b/JustDummies.UnitTests/NullArgumentGuardConventionTests.cs
new file mode 100644
index 00000000..1bd255a0
--- /dev/null
+++ b/JustDummies.UnitTests/NullArgumentGuardConventionTests.cs
@@ -0,0 +1,456 @@
+#region Usings declarations
+
+using System.Collections;
+using System.Linq.Expressions;
+using System.Reflection;
+
+using NFluent;
+
+#endregion
+
+namespace JustDummies.UnitTests;
+
+///
+/// The null-argument guard convention, enforced by reflection over the whole library surface: every
+/// public or internal member (constructor or method) that takes a non-nullable reference-type
+/// argument must reject null with an naming the offending parameter.
+/// A caller — even another class of this assembly — is outside the class it calls, so the boundary validates
+/// what crosses it; only what a validating member has already accepted is trusted inside.
+///
+///
+///
+/// This is a single self-maintaining guard: it discovers members through reflection, so a new generator,
+/// factory, or fluent method is held to the convention automatically, with nothing to add here. Value-type
+/// and nullable (? ) parameters are excluded by design — the former cannot be null , the latter
+/// are deliberately optional. Exception types are excluded too: constructing an exception must never itself
+/// throw while an error is being handled or logged.
+///
+///
+/// Testing the internal boundary (the Create factories and internal constructors the public API can
+/// never route a null through) requires reaching internals, which is why JustDummies opens them to
+/// this suite — see ADR-0045. The test uses .NET 6+ reflection nullability metadata, so it runs on the
+/// modern leg only and is excluded from the net472 support-floor build.
+///
+///
+public sealed class NullArgumentGuardConventionTests {
+
+ private static readonly Assembly LibraryAssembly = typeof(Any).Assembly;
+ private static readonly NullabilityInfoContext Nullability = new();
+ private static readonly List Samples = HarvestSamples();
+
+ [Fact(DisplayName = "Every public/internal member rejects a null non-nullable reference argument with ArgumentNullException.")]
+ public void EveryPublicOrInternalMemberGuardsItsNonNullableReferenceArguments() {
+ List violations = [];
+ List uncovered = [];
+
+ foreach (Type declared in LibraryAssembly.GetTypes()) {
+ if (!IsInScope(declared)) { continue; }
+
+ Type type = declared;
+ if (declared.IsGenericTypeDefinition) {
+ if (declared.IsAbstract) { continue; } // an abstract base (e.g. AnyCollection`3): covered through its concrete subclasses
+ if (!TryClose(declared, out type!)) {
+ uncovered.Add($"type {declared.Name}: could not close generic definition");
+ continue;
+ }
+ }
+
+ foreach (MemberDescriptor member in MembersOf(type)) {
+ ProcessMember(member, violations, uncovered);
+ }
+ }
+
+ // A parameter the harness could not exercise is a hole in the convention's coverage, not a pass: it is
+ // reported alongside the missing guards so it is either given a sample here or covered by an explicit test,
+ // never silently skipped.
+ List failures = uncovered.Select(entry => $"[uncovered] {entry}")
+ .Concat(violations.Select(entry => $"[missing-guard] {entry}"))
+ .OrderBy(line => line, StringComparer.Ordinal)
+ .ToList();
+
+ Check.WithCustomMessage(
+ $"Null-argument guard convention — {violations.Count} member(s) missing a guard, "
+ + $"{uncovered.Count} parameter(s) the harness could not exercise:{Environment.NewLine}"
+ + string.Join(Environment.NewLine, failures))
+ .That(failures)
+ .IsEmpty();
+ }
+
+ #region Member enumeration
+
+ private readonly struct MemberDescriptor(MethodBase invoke, MethodBase classify, Type receiver) {
+
+ public MethodBase Invoke { get; } = invoke; // closed and callable
+ public MethodBase Classify { get; } = classify; // open enough to read annotations and generic constraints
+ public Type Receiver { get; } = receiver; // the (closed) type an instance member is called on
+
+ }
+
+ private static IEnumerable MembersOf(Type type) {
+ bool instantiable = type is { IsAbstract: false, IsInterface: false };
+
+ if (instantiable) {
+ foreach (ConstructorInfo ctor in type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
+ if (IsAccessible(ctor)) { yield return new MemberDescriptor(ctor, ctor, type); }
+ }
+ }
+
+ BindingFlags methodFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
+ if (type.IsAbstract) { methodFlags |= BindingFlags.DeclaredOnly; } // instance methods of an abstract base are reached through its concrete subclasses
+
+ foreach (MethodInfo method in type.GetMethods(methodFlags)) {
+ if (method.DeclaringType == typeof(object)) { continue; }
+ if (method.IsSpecialName) { continue; } // property/event accessors, operators
+ if (method.Name.Contains('<')) { continue; } // compiler-generated
+ if (!IsAccessible(method)) { continue; }
+ if (method is { IsAbstract: true }) { continue; } // no body to reach directly
+
+ if (method.IsGenericMethodDefinition) {
+ if (!TryClose(method, out MethodInfo? closed)) { continue; }
+ yield return new MemberDescriptor(closed!, method, type);
+ } else {
+ yield return new MemberDescriptor(method, method, type);
+ }
+ }
+ }
+
+ private static bool IsInScope(Type type) {
+ if (type.IsInterface || type.IsEnum) { return false; }
+ if (typeof(Delegate).IsAssignableFrom(type)) { return false; }
+ if (typeof(Exception).IsAssignableFrom(type)) { return false; }
+ if (type.Name.StartsWith('<')) { return false; }
+ if (type.GetCustomAttribute() is not null) { return false; }
+
+ if (type.IsNested) {
+ return type.IsNestedPublic || type.IsNestedAssembly || type.IsNestedFamORAssem;
+ }
+
+ return type.IsPublic || type.IsNotPublic; // top-level public or internal
+ }
+
+ private static bool IsAccessible(MethodBase member) {
+ return member.IsPublic || member.IsAssembly || member.IsFamilyOrAssembly; // public / internal / protected internal
+ }
+
+ #endregion
+
+ #region Per-member verification
+
+ private static void ProcessMember(MemberDescriptor member, List violations, List uncovered) {
+ ParameterInfo[] invokeParams = member.Invoke.GetParameters();
+ ParameterInfo[] classifyParams = ClassificationParameters(member);
+
+ // A validation helper that carries a caller-supplied name (RequireHost(host, parameterName)) reports that
+ // forwarded name, not its own — so for such members any ArgumentNullException, whatever its ParamName, honours
+ // the convention. Whether an exception is thrown at all is still checked strictly.
+ bool forwardsName = invokeParams.Any(parameter => parameter.Name is "parameterName" or "paramName");
+
+ for (int index = 0; index < invokeParams.Length; index++) {
+ ParameterInfo classify = classifyParams[index];
+ if (!IsGuardTarget(classify)) { continue; }
+
+ string description = Describe(member, classify);
+
+ object?[] arguments;
+ object? instance;
+ try {
+ instance = member.Invoke.IsStatic || member.Invoke is ConstructorInfo ? null : Sample(member.Receiver);
+ arguments = new object?[invokeParams.Length];
+ for (int j = 0; j < invokeParams.Length; j++) {
+ arguments[j] = j == index ? null : ArgumentFor(invokeParams[j]);
+ }
+ } catch (Exception build) {
+ uncovered.Add($"{description}: could not build arguments — {Root(build).Message}");
+ continue;
+ }
+
+ try {
+ if (member.Invoke is ConstructorInfo ctor) { ctor.Invoke(arguments); } else { member.Invoke.Invoke(instance, arguments); }
+ violations.Add($"{description}: expected ArgumentNullException, nothing was thrown");
+ } catch (TargetInvocationException invocation) {
+ Exception? thrown = invocation.InnerException;
+ if (thrown is ArgumentNullException guard && (guard.ParamName == classify.Name || forwardsName)) { continue; }
+
+ string got = thrown is ArgumentNullException other
+ ? $"ArgumentNullException(ParamName=\"{other.ParamName}\")"
+ : thrown?.GetType().Name ?? "null";
+ violations.Add($"{description}: expected ArgumentNullException(\"{classify.Name}\") but got {got}");
+ } catch (Exception unexpected) {
+ uncovered.Add($"{description}: invocation failed — {Root(unexpected).GetType().Name}: {Root(unexpected).Message}");
+ }
+ }
+ }
+
+ // Parameters to read annotations and generic constraints from: for a generic method, the open definition (which
+ // still carries `T` and its constraints); for a member of a constructed generic type, the definition member
+ // (whose parameters carry the nullable annotations the constructed copy elides).
+ private static ParameterInfo[] ClassificationParameters(MemberDescriptor member) {
+ if (member.Classify is MethodInfo { IsGenericMethodDefinition: true } definition) { return definition.GetParameters(); }
+
+ Type? declaring = member.Invoke.DeclaringType;
+ if (declaring is { IsGenericType: true, IsGenericTypeDefinition: false }) {
+ try {
+ Type openDeclaring = declaring.GetGenericTypeDefinition();
+ if (openDeclaring.Module.ResolveMember(member.Invoke.MetadataToken) is MethodBase open) { return open.GetParameters(); }
+ } catch {
+ // fall through to the constructed parameters
+ }
+ }
+
+ return member.Invoke.GetParameters();
+ }
+
+ private static bool IsGuardTarget(ParameterInfo parameter) {
+ Type type = parameter.ParameterType;
+ if (type.IsByRef) { return false; } // out/ref/in
+
+ if (type.IsGenericParameter) {
+ return (type.GenericParameterAttributes & GenericParameterAttributes.ReferenceTypeConstraint) != 0;
+ }
+
+ if (type.IsValueType || type.IsPointer) { return false; } // includes Nullable, enums, structs
+
+ try {
+ return Nullability.Create(parameter).ReadState == NullabilityState.NotNull;
+ } catch {
+ return true; // unknown annotation → treat as a target so any gap shows up as a visible violation
+ }
+ }
+
+ private static string Describe(MemberDescriptor member, ParameterInfo parameter) {
+ string kind = member.Invoke is ConstructorInfo ? "ctor" : member.Invoke.Name;
+ string signature = string.Join(", ", member.Invoke.GetParameters().Select(p => $"{Readable(p.ParameterType)} {p.Name}"));
+
+ return $"{Readable(member.Receiver)}.{kind}({signature}) [param '{parameter.Name}']";
+ }
+
+ #endregion
+
+ #region Sample values
+
+ private static object? ArgumentFor(ParameterInfo parameter) {
+ Type type = parameter.ParameterType;
+ if (type.IsByRef) { type = type.GetElementType()!; }
+
+ if (type.IsValueType) {
+ return Nullable.GetUnderlyingType(type) is not null ? null : Activator.CreateInstance(type);
+ }
+
+ // A nullable non-target reference can simply be null; a non-nullable one needs a real, valid value so the
+ // member reaches (and only reaches) the guard of the parameter under test.
+ try {
+ if (Nullability.Create(parameter).ReadState == NullabilityState.Nullable) { return null; }
+ } catch {
+ // fall through and build a value
+ }
+
+ return Sample(type);
+ }
+
+ private static object Sample(Type type) {
+ foreach (object candidate in Samples) {
+ if (type.IsInstanceOfType(candidate)) { return candidate; }
+ }
+
+ if (type == typeof(string)) { return "sample"; }
+
+ if (type.IsArray) {
+ Type element = type.GetElementType()!;
+ Array array = Array.CreateInstance(element, 1);
+ array.SetValue(Element(element), 0);
+
+ return array;
+ }
+
+ if (type.IsGenericType) {
+ Type definition = type.GetGenericTypeDefinition();
+ Type[] arguments = type.GetGenericArguments();
+
+ if (definition == typeof(IAny<>)) { return CreateAny(arguments[0]); }
+
+ if (definition == typeof(IEnumerable<>) || definition == typeof(IReadOnlyList<>) || definition == typeof(IReadOnlyCollection<>)
+ || definition == typeof(IList<>) || definition == typeof(ICollection<>) || definition == typeof(List<>)) {
+ Type listType = typeof(List<>).MakeGenericType(arguments[0]);
+ var list = (IList)Activator.CreateInstance(listType)!;
+ list.Add(Element(arguments[0]));
+
+ return list;
+ }
+
+ if (definition == typeof(IReadOnlyDictionary<,>) || definition == typeof(IDictionary<,>) || definition == typeof(Dictionary<,>)) {
+ Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(arguments);
+ var dictionary = (IDictionary)Activator.CreateInstance(dictionaryType)!;
+ dictionary[Element(arguments[0])!] = Element(arguments[1]);
+
+ return dictionary;
+ }
+ }
+
+ if (typeof(Delegate).IsAssignableFrom(type)) { return CreateDelegate(type); }
+
+ throw new NotSupportedException($"no sample available for {Readable(type)}");
+ }
+
+ private static object? Element(Type type) {
+ if (type == typeof(string)) { return "x"; }
+ if (type.IsValueType) { return Nullable.GetUnderlyingType(type) is not null ? null : Activator.CreateInstance(type); }
+
+ return Sample(type);
+ }
+
+ private static object CreateAny(Type valueType) {
+ MethodInfo oneOf = typeof(Any).GetMethods(BindingFlags.Public | BindingFlags.Static)
+ .First(method => method is { Name: "OneOf", IsGenericMethodDefinition: true }
+ && method.GetParameters() is [{ ParameterType.IsArray: true }])
+ .MakeGenericMethod(valueType);
+
+ Array pool = Array.CreateInstance(valueType, 1);
+ pool.SetValue(Element(valueType), 0);
+
+ return oneOf.Invoke(null, [pool])!;
+ }
+
+ private static object CreateDelegate(Type delegateType) {
+ MethodInfo signature = delegateType.GetMethod("Invoke")!;
+ ParameterExpression[] inputs = signature.GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
+ Type returnType = signature.ReturnType;
+
+ Expression body = returnType == typeof(void)
+ ? Expression.Empty()
+ : Expression.Constant(Element(returnType), returnType);
+
+ return Expression.Lambda(delegateType, body, inputs).Compile();
+ }
+
+ // A pool of live, valid internal instances (random sources, interval/string/uri specs, collection state, regex
+ // nodes, ...) harvested by walking the object graph of a handful of real generators. Reusing what the library
+ // itself builds is what lets the harness supply a valid `spec` when testing a `source` guard, without wiring up
+ // each engine type by hand.
+ private static List HarvestSamples() {
+ List pool = [];
+ HashSet visited = new(ReferenceEqualityComparer.Instance);
+ Queue frontier = new();
+
+ void Seed(object? root) {
+ if (root is null or string) { return; }
+ if (root.GetType().IsValueType) { return; } // primitives, enums, structs are never harvested samples
+ if (visited.Add(root)) { frontier.Enqueue(root); }
+ }
+
+ AnyContext context = new(0);
+ Seed(context);
+ Seed(new FixedRandomSource(0));
+ Seed(new SeededRandom(0));
+
+ foreach (MethodInfo factory in typeof(AnyContext).GetMethods(BindingFlags.Public | BindingFlags.Instance)) {
+ if (factory.DeclaringType == typeof(object)) { continue; }
+ if (factory.IsSpecialName || factory.GetParameters().Length != 0) { continue; }
+ if (factory.ReturnType == typeof(void) || factory.ReturnType == typeof(AnyContext) || factory.IsGenericMethodDefinition) { continue; }
+ try { Seed(factory.Invoke(context, null)); } catch { /* best effort */ }
+ }
+
+ IAny strings = Any.String();
+ void Try(Func build) {
+ try { Seed(build()); } catch { /* best effort */ }
+ }
+
+ // Collections closed on the representative element type the harness uses (string), plus a dictionary and an enum.
+ Try(() => Any.ListOf(strings));
+ Try(() => Any.SetOf(strings));
+ Try(() => Any.SequenceOf(strings));
+ Try(() => Any.ArrayOf(strings));
+ Try(() => Any.DictionaryOf(strings, strings));
+ Try(() => Any.OneOf("a", "b", "c"));
+ Try(() => Any.Enum());
+ Try(() => strings.As(value => value.Length));
+ Try(() => strings.OrNull());
+
+ // One pattern per regex-node shape, so every RegexNode subtype is reachable through AnyPattern's tree.
+ Try(() => Any.StringMatching("abc")); // sequence
+ Try(() => Any.StringMatching("a|b|c")); // alternation
+ Try(() => Any.StringMatching("a{2,3}")); // repeat
+ Try(() => Any.StringMatching("[a-z]")); // characters
+
+ int budget = 0;
+ while (frontier.Count > 0 && budget++ < 5_000) {
+ object current = frontier.Dequeue();
+ pool.Add(current);
+
+ for (Type? level = current.GetType(); level is not null && level != typeof(object); level = level.BaseType) {
+ foreach (FieldInfo field in level.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
+ Seed(field.GetValue(current));
+ }
+ }
+
+ // Follow parameterless generator-producing steps too, so a sibling generator reachable only through a
+ // fluent call (e.g. the concrete URI generators returned by AnyUri) still enters the pool.
+ if (current.GetType().Assembly != LibraryAssembly) { continue; }
+ foreach (MethodInfo step in current.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)) {
+ if (step.DeclaringType == typeof(object) || step.IsSpecialName || step.IsGenericMethodDefinition) { continue; }
+ if (step.GetParameters().Length != 0 || step.ReturnType.Assembly != LibraryAssembly) { continue; }
+ try { Seed(step.Invoke(current, null)); } catch { /* best effort */ }
+ }
+ }
+
+ return pool;
+ }
+
+ #endregion
+
+ #region Helpers
+
+ private static bool TryClose(Type definition, out Type? closed) {
+ try {
+ closed = definition.MakeGenericType(definition.GetGenericArguments().Select(Representative).ToArray());
+
+ return true;
+ } catch {
+ closed = null;
+
+ return false;
+ }
+ }
+
+ private static bool TryClose(MethodInfo definition, out MethodInfo? closed) {
+ try {
+ closed = definition.MakeGenericMethod(definition.GetGenericArguments().Select(Representative).ToArray());
+
+ return true;
+ } catch {
+ closed = null;
+
+ return false;
+ }
+ }
+
+ private static Type Representative(Type parameter) {
+ GenericParameterAttributes attributes = parameter.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
+ Type[] constraints = parameter.GetGenericParameterConstraints();
+
+ if ((attributes & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0) {
+ return constraints.Any(constraint => constraint == typeof(Enum) || constraint.BaseType == typeof(Enum)) ? typeof(DayOfWeek) : typeof(int);
+ }
+
+ Type? baseConstraint = constraints.FirstOrDefault(constraint => constraint is { IsClass: true } && constraint != typeof(object));
+ if (baseConstraint is not null) { return baseConstraint; }
+
+ return constraints.All(constraint => !constraint.IsInterface || constraint.IsAssignableFrom(typeof(string))) ? typeof(string) : typeof(int);
+ }
+
+ private static Exception Root(Exception exception) {
+ return exception is TargetInvocationException { InnerException: { } inner } ? inner : exception;
+ }
+
+ private static string Readable(Type type) {
+ if (!type.IsGenericType) { return type.Name; }
+
+ string name = type.Name;
+ int tick = name.IndexOf('`');
+ if (tick >= 0) { name = name[..tick]; }
+
+ return $"{name}<{string.Join(", ", type.GetGenericArguments().Select(Readable))}>";
+ }
+
+ #endregion
+
+}
diff --git a/JustDummies/Any.cs b/JustDummies/Any.cs
index c509326f..5fed7a66 100644
--- a/JustDummies/Any.cs
+++ b/JustDummies/Any.cs
@@ -550,6 +550,8 @@ public static void Reproducibly(int seed, Action body, Action? report =
/// A task that completes when completes, and faults with the body's exception.
/// Thrown when is null .
public static Task ReproduciblyAsync(Func body, Action? report = null) {
+ if (body is null) { throw new ArgumentNullException(nameof(body)); }
+
return ReproduciblyAsync(AmbientRandomSource.NewSeed(), body, report);
}
@@ -562,9 +564,15 @@ public static Task ReproduciblyAsync(Func body, Action? report = n
/// The sink the seed is written to on failure. Defaults to when null .
/// A task that completes when completes.
/// Thrown when is null .
- public static async Task ReproduciblyAsync(int seed, Func body, Action? report = null) {
+ public static Task ReproduciblyAsync(int seed, Func body, Action? report = null) {
if (body is null) { throw new ArgumentNullException(nameof(body)); }
+ return RunReproduciblyAsync(seed, body, report);
+ }
+
+ // Kept separate from the public entry so the null-argument guard above throws synchronously at the call site,
+ // rather than being deferred into the returned task's fault — which a caller who forgets to await would miss.
+ private static async Task RunReproduciblyAsync(int seed, Func body, Action? report) {
using (AmbientRandomSource.UseSeed(seed)) {
try {
await body().ConfigureAwait(false);
diff --git a/JustDummies/AnyArray.cs b/JustDummies/AnyArray.cs
index 2d25dce4..e63a9d75 100644
--- a/JustDummies/AnyArray.cs
+++ b/JustDummies/AnyArray.cs
@@ -29,10 +29,14 @@ public AnyArray Distinct(IEqualityComparer comparer) {
}
private protected override AnyArray With(CollectionState state) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
+
return new AnyArray(SourceOrNull, state);
}
private protected override T[] Build(List items) {
+ if (items is null) { throw new ArgumentNullException(nameof(items)); }
+
return items.ToArray();
}
diff --git a/JustDummies/AnyBoolean.cs b/JustDummies/AnyBoolean.cs
index 8ffd354c..86f14e1e 100644
--- a/JustDummies/AnyBoolean.cs
+++ b/JustDummies/AnyBoolean.cs
@@ -10,6 +10,8 @@ public sealed class AnyBoolean : IAny, IHasRandomSource, ICardinalityHint<
#region Statics members declarations
internal static AnyBoolean Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyBoolean(source, null, null);
}
diff --git a/JustDummies/AnyByte.cs b/JustDummies/AnyByte.cs
index b768a197..0158e253 100644
--- a/JustDummies/AnyByte.cs
+++ b/JustDummies/AnyByte.cs
@@ -17,6 +17,8 @@ public sealed class AnyByte : IAny, IHasRandomSource, ICardinalityHint V(Val(ordinal)), Ord(byte.MinValue), Ord(byte.MaxValue)));
}
diff --git a/JustDummies/AnyChar.cs b/JustDummies/AnyChar.cs
index 71879f4a..e0b8ae35 100644
--- a/JustDummies/AnyChar.cs
+++ b/JustDummies/AnyChar.cs
@@ -13,6 +13,8 @@ public sealed class AnyChar : IAny, IHasRandomSource, ICardinalityHint : IAny, IHas
#endregion
private protected AnyCollection(RandomSource? source, CollectionState state) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
+
SourceOrNull = source;
State = state;
}
diff --git a/JustDummies/AnyDateOnly.cs b/JustDummies/AnyDateOnly.cs
index 42187a24..bfcb8caa 100644
--- a/JustDummies/AnyDateOnly.cs
+++ b/JustDummies/AnyDateOnly.cs
@@ -21,6 +21,8 @@ public sealed class AnyDateOnly : IAny, IHasRandomSource, ICardinality
#region Statics members declarations
internal static AnyDateOnly Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyDateOnly(source, OrdinalIntervalSpec.Unconstrained("DateOnly", ordinal => V(Val(ordinal)), Ord(DateOnly.MinValue), Ord(DateOnly.MaxValue)));
}
diff --git a/JustDummies/AnyDateTime.cs b/JustDummies/AnyDateTime.cs
index 5b2c6fc9..e55e6c58 100644
--- a/JustDummies/AnyDateTime.cs
+++ b/JustDummies/AnyDateTime.cs
@@ -24,6 +24,8 @@ public sealed class AnyDateTime : IAny, IHasRandomSource, ICardinality
#region Statics members declarations
internal static AnyDateTime Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyDateTime(source, OrdinalIntervalSpec.Unconstrained("DateTime", ordinal => V(Val(ordinal)), Ord(DateTime.MinValue), Ord(DateTime.MaxValue)));
}
diff --git a/JustDummies/AnyDateTimeOffset.cs b/JustDummies/AnyDateTimeOffset.cs
index fe64034c..83f023b2 100644
--- a/JustDummies/AnyDateTimeOffset.cs
+++ b/JustDummies/AnyDateTimeOffset.cs
@@ -30,6 +30,8 @@ public sealed class AnyDateTimeOffset : IAny, IHasRandomSource,
#region Statics members declarations
internal static AnyDateTimeOffset Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyDateTimeOffset(source, OrdinalIntervalSpec.Unconstrained("DateTimeOffset", ordinal => V(Val(ordinal)), Ord(DateTimeOffset.MinValue), Ord(DateTimeOffset.MaxValue)), null, false, 0, 0);
}
diff --git a/JustDummies/AnyDecimal.cs b/JustDummies/AnyDecimal.cs
index be693d62..436d4bda 100644
--- a/JustDummies/AnyDecimal.cs
+++ b/JustDummies/AnyDecimal.cs
@@ -18,6 +18,8 @@ public sealed class AnyDecimal : IAny, IHasRandomSource, ICardinalityHi
#region Statics members declarations
internal static AnyDecimal Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyDecimal(source, DecimalIntervalSpec.Unconstrained("Decimal", V));
}
diff --git a/JustDummies/AnyDerivation.cs b/JustDummies/AnyDerivation.cs
index 9f0ced3e..27ace873 100644
--- a/JustDummies/AnyDerivation.cs
+++ b/JustDummies/AnyDerivation.cs
@@ -25,6 +25,8 @@ internal sealed class DerivedAny : IAny, IHasRandomSource, IReproducibilit
#endregion
internal DerivedAny(RandomSource? source, bool drawsOnlyFromSource, Func generate) {
+ if (generate is null) { throw new ArgumentNullException(nameof(generate)); }
+
_source = source;
_drawsOnlyFromSource = drawsOnlyFromSource;
_generate = generate;
@@ -46,6 +48,8 @@ internal static class AnyDerivation {
/// The random context of , when it is one of the library's own.
internal static RandomSource? SourceOf(IAny generator) {
+ if (generator is null) { throw new ArgumentNullException(nameof(generator)); }
+
return (generator as IHasRandomSource)?.Source;
}
@@ -58,6 +62,8 @@ internal static class AnyDerivation {
/// replayed from that seed.
///
internal static bool IsReproducible(IAny generator) {
+ if (generator is null) { throw new ArgumentNullException(nameof(generator)); }
+
if (generator is IReproducibilityHint hint) { return hint.DrawsOnlyFromSource; }
return SourceOf(generator) is not null;
@@ -68,6 +74,8 @@ internal static bool IsReproducible(IAny generator) {
/// advertises one through ; null when the domain is unbounded or unknown.
///
internal static long? CardinalityOf(IAny generator) {
+ if (generator is null) { throw new ArgumentNullException(nameof(generator)); }
+
return (generator as ICardinalityHint)?.DistinctCardinality;
}
@@ -79,6 +87,9 @@ internal static bool IsReproducible(IAny generator) {
/// promising a full replay the seed cannot deliver. The library's own exceptions pass through untouched.
///
internal static T Invoke(Func invoke, RandomSource? source, bool reproducible, string failure) {
+ if (invoke is null) { throw new ArgumentNullException(nameof(invoke)); }
+ if (failure is null) { throw new ArgumentNullException(nameof(failure)); }
+
try {
return invoke();
} catch (AnyException) {
diff --git a/JustDummies/AnyDictionary.cs b/JustDummies/AnyDictionary.cs
index b96a8123..6a49ffda 100644
--- a/JustDummies/AnyDictionary.cs
+++ b/JustDummies/AnyDictionary.cs
@@ -28,7 +28,10 @@ public sealed class AnyDictionary : IAny>
#endregion
internal AnyDictionary(RandomSource? source, CollectionState keys, IAny values)
- : this(source, keys, values, NoPinnedValues) { }
+ : this(source, keys, values, NoPinnedValues) {
+ if (keys is null) { throw new ArgumentNullException(nameof(keys)); }
+ if (values is null) { throw new ArgumentNullException(nameof(values)); }
+ }
private AnyDictionary(RandomSource? source, CollectionState keys, IAny values, IReadOnlyDictionary pinnedValues) {
_source = source;
diff --git a/JustDummies/AnyDouble.cs b/JustDummies/AnyDouble.cs
index 3cb12909..97821497 100644
--- a/JustDummies/AnyDouble.cs
+++ b/JustDummies/AnyDouble.cs
@@ -17,6 +17,8 @@ public sealed class AnyDouble : IAny, IHasRandomSource, ICardinalityHint
#region Statics members declarations
internal static AnyDouble Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyDouble(source, ContinuousIntervalSpec.Unconstrained("Double", V, value => value, ContinuousIntervalSpec.NextUp, -double.MaxValue, double.MaxValue));
}
diff --git a/JustDummies/AnyEnum.cs b/JustDummies/AnyEnum.cs
index f01189ba..36294c10 100644
--- a/JustDummies/AnyEnum.cs
+++ b/JustDummies/AnyEnum.cs
@@ -43,6 +43,7 @@ public sealed class AnyEnum : IAny, IHasRandomSource, ICardinality
private static TEnum[]? _combinations;
internal static AnyEnum Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
if (Declared.Length == 0) {
throw new AnyGenerationException($"Cannot generate an arbitrary {typeof(TEnum).Name} value because the enum declares no members.");
}
diff --git a/JustDummies/AnyFtpUri.cs b/JustDummies/AnyFtpUri.cs
index 280b4484..a6fd5bbd 100644
--- a/JustDummies/AnyFtpUri.cs
+++ b/JustDummies/AnyFtpUri.cs
@@ -14,6 +14,8 @@ public sealed class AnyFtpUri : IAny, IHasRandomSource {
#endregion
internal AnyFtpUri(RandomSource source, UriSpec spec) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (spec is null) { throw new ArgumentNullException(nameof(spec)); }
_source = source;
_spec = spec;
}
diff --git a/JustDummies/AnyGuid.cs b/JustDummies/AnyGuid.cs
index cd6926b5..d357e9f0 100644
--- a/JustDummies/AnyGuid.cs
+++ b/JustDummies/AnyGuid.cs
@@ -13,6 +13,8 @@ public sealed class AnyGuid : IAny, IHasRandomSource, ICardinalityHint, IHasRandomSource, ICardinalityHint V((Half)value), value => (double)(Half)value, value => NextUp((Half)value), -(double)Half.MaxValue, (double)Half.MaxValue));
}
diff --git a/JustDummies/AnyInt128.cs b/JustDummies/AnyInt128.cs
index a61d8b4e..7f60c353 100644
--- a/JustDummies/AnyInt128.cs
+++ b/JustDummies/AnyInt128.cs
@@ -19,6 +19,8 @@ public sealed class AnyInt128 : IAny, IHasRandomSource, ICardinalityHint
#region Statics members declarations
internal static AnyInt128 Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyInt128(source, WideIntervalSpec.Unconstrained("Int128", ordinal => V(Val(ordinal)), Ord(Int128.MinValue), Ord(Int128.MaxValue)));
}
diff --git a/JustDummies/AnyInt16.cs b/JustDummies/AnyInt16.cs
index c1258911..31bede8f 100644
--- a/JustDummies/AnyInt16.cs
+++ b/JustDummies/AnyInt16.cs
@@ -17,6 +17,8 @@ public sealed class AnyInt16 : IAny, IHasRandomSource, ICardinalityHint V(Val(ordinal)), Ord(short.MinValue), Ord(short.MaxValue)));
}
diff --git a/JustDummies/AnyInt32.cs b/JustDummies/AnyInt32.cs
index 429c47e0..dcf308bd 100644
--- a/JustDummies/AnyInt32.cs
+++ b/JustDummies/AnyInt32.cs
@@ -33,6 +33,8 @@ public sealed class AnyInt32 : IAny, IHasRandomSource, ICardinalityHint V(Val(ordinal)), Ord(int.MinValue), Ord(int.MaxValue)));
}
diff --git a/JustDummies/AnyInt64.cs b/JustDummies/AnyInt64.cs
index 4549ceaf..8e0bf9a2 100644
--- a/JustDummies/AnyInt64.cs
+++ b/JustDummies/AnyInt64.cs
@@ -17,6 +17,8 @@ public sealed class AnyInt64 : IAny, IHasRandomSource, ICardinalityHint V(Val(ordinal)), Ord(long.MinValue), Ord(long.MaxValue)));
}
diff --git a/JustDummies/AnyList.cs b/JustDummies/AnyList.cs
index f71fe051..afd2dc76 100644
--- a/JustDummies/AnyList.cs
+++ b/JustDummies/AnyList.cs
@@ -29,10 +29,14 @@ public AnyList Distinct(IEqualityComparer comparer) {
}
private protected override AnyList With(CollectionState state) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
+
return new AnyList(SourceOrNull, state);
}
private protected override List Build(List items) {
+ if (items is null) { throw new ArgumentNullException(nameof(items)); }
+
return items;
}
diff --git a/JustDummies/AnyMailtoUri.cs b/JustDummies/AnyMailtoUri.cs
index 35df59a5..09101b5f 100644
--- a/JustDummies/AnyMailtoUri.cs
+++ b/JustDummies/AnyMailtoUri.cs
@@ -16,6 +16,8 @@ public sealed class AnyMailtoUri : IAny, IHasRandomSource {
#endregion
internal AnyMailtoUri(RandomSource source, UriSpec spec) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (spec is null) { throw new ArgumentNullException(nameof(spec)); }
_source = source;
_spec = spec;
}
diff --git a/JustDummies/AnyOneOf.cs b/JustDummies/AnyOneOf.cs
index bc2ac711..dccbd155 100644
--- a/JustDummies/AnyOneOf.cs
+++ b/JustDummies/AnyOneOf.cs
@@ -34,9 +34,12 @@ public sealed class AnyOneOf : IAny, IHasRandomSource, ICardinalityHint
#region Statics members declarations
- // Validates and deduplicates the caller's pool, then builds the generator. The array-null check belongs to the
- // public factories (they own the parameter name); by the time we get here the pool is non-null and materialized.
+ // Validates and deduplicates the caller's pool, then builds the generator. As an internal boundary it guards its
+ // own arguments per the null-argument convention (ADR-0045); the public factories additionally reject a null
+ // array first, under the caller-facing parameter name, before delegating here.
internal static AnyOneOf FromPool(RandomSource source, IReadOnlyList values) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (values is null) { throw new ArgumentNullException(nameof(values)); }
if (values.Count == 0) { throw new ArgumentException("At least one value is required.", nameof(values)); }
if (values.Any(value => value is null)) { throw new ArgumentException("The values must not contain a null element; use OrNull() to make the whole generator nullable.", nameof(values)); }
diff --git a/JustDummies/AnyPattern.cs b/JustDummies/AnyPattern.cs
index e1429140..aba3dd13 100644
--- a/JustDummies/AnyPattern.cs
+++ b/JustDummies/AnyPattern.cs
@@ -36,6 +36,8 @@ public sealed class AnyPattern : IAny, IHasRandomSource {
private const int GenerationLimit = 65536;
internal static AnyPattern FromPattern(RandomSource source, string pattern, bool ignoreCase) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (pattern is null) { throw new ArgumentNullException(nameof(pattern)); }
return new AnyPattern(source, RegexParser.Parse(pattern, ignoreCase));
}
@@ -49,6 +51,8 @@ internal static AnyPattern FromPattern(RandomSource source, string pattern, bool
#endregion
internal AnyPattern(RandomSource source, RegexNode root) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (root is null) { throw new ArgumentNullException(nameof(root)); }
_source = source;
_root = root;
}
diff --git a/JustDummies/AnyRelativeUri.cs b/JustDummies/AnyRelativeUri.cs
index 4e66b182..ec8779b9 100644
--- a/JustDummies/AnyRelativeUri.cs
+++ b/JustDummies/AnyRelativeUri.cs
@@ -16,6 +16,8 @@ public sealed class AnyRelativeUri : IAny, IHasRandomSource {
#endregion
internal AnyRelativeUri(RandomSource source, UriSpec spec) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (spec is null) { throw new ArgumentNullException(nameof(spec)); }
_source = source;
_spec = spec;
}
diff --git a/JustDummies/AnySByte.cs b/JustDummies/AnySByte.cs
index 741a1462..250ee6be 100644
--- a/JustDummies/AnySByte.cs
+++ b/JustDummies/AnySByte.cs
@@ -17,6 +17,8 @@ public sealed class AnySByte : IAny, IHasRandomSource, ICardinalityHint V(Val(ordinal)), Ord(sbyte.MinValue), Ord(sbyte.MaxValue)));
}
diff --git a/JustDummies/AnySequence.cs b/JustDummies/AnySequence.cs
index b36588b5..28278e84 100644
--- a/JustDummies/AnySequence.cs
+++ b/JustDummies/AnySequence.cs
@@ -35,10 +35,14 @@ public AnySequence Distinct(IEqualityComparer comparer) {
}
private protected override AnySequence With(CollectionState state) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
+
return new AnySequence(SourceOrNull, state);
}
private protected override IEnumerable Build(List items) {
+ if (items is null) { throw new ArgumentNullException(nameof(items)); }
+
return items;
}
diff --git a/JustDummies/AnySet.cs b/JustDummies/AnySet.cs
index a6fb0bd4..13cf3448 100644
--- a/JustDummies/AnySet.cs
+++ b/JustDummies/AnySet.cs
@@ -14,10 +14,14 @@ public sealed class AnySet : AnyCollection, AnySet> {
internal AnySet(RandomSource? source, CollectionState state) : base(source, state) { }
private protected override AnySet With(CollectionState state) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
+
return new AnySet(SourceOrNull, state);
}
private protected override HashSet Build(List items) {
+ if (items is null) { throw new ArgumentNullException(nameof(items)); }
+
// The state already deduplicated under the comparer; the set carries the same comparer so later lookups
// by the caller behave identically.
return new HashSet(items, State.Comparer);
diff --git a/JustDummies/AnySingle.cs b/JustDummies/AnySingle.cs
index 51050c7b..79ad4dfe 100644
--- a/JustDummies/AnySingle.cs
+++ b/JustDummies/AnySingle.cs
@@ -17,6 +17,8 @@ public sealed class AnySingle : IAny, IHasRandomSource, ICardinalityHint<
#region Statics members declarations
internal static AnySingle Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnySingle(source, ContinuousIntervalSpec.Unconstrained("Single", value => V((float)value), value => (float)value, value => NextUp((float)value), -float.MaxValue, float.MaxValue));
}
diff --git a/JustDummies/AnyString.cs b/JustDummies/AnyString.cs
index 671a5659..eab80a49 100644
--- a/JustDummies/AnyString.cs
+++ b/JustDummies/AnyString.cs
@@ -69,6 +69,8 @@ private static int RequireNonNegative(int length, string parameterName) {
#endregion
internal AnyString(RandomSource source, StringSpec spec) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (spec is null) { throw new ArgumentNullException(nameof(spec)); }
_source = source;
_spec = spec;
}
diff --git a/JustDummies/AnyStringOneOf.cs b/JustDummies/AnyStringOneOf.cs
index 8f09ec54..94f10b11 100644
--- a/JustDummies/AnyStringOneOf.cs
+++ b/JustDummies/AnyStringOneOf.cs
@@ -33,6 +33,8 @@ public sealed class AnyStringOneOf : IAny, IHasRandomSource, ICardinalit
#endregion
internal AnyStringOneOf(RandomSource source, IReadOnlyList values) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (values is null) { throw new ArgumentNullException(nameof(values)); }
_source = source;
_values = values;
}
diff --git a/JustDummies/AnyTimeOnly.cs b/JustDummies/AnyTimeOnly.cs
index d8340de6..2fb8ac54 100644
--- a/JustDummies/AnyTimeOnly.cs
+++ b/JustDummies/AnyTimeOnly.cs
@@ -21,6 +21,8 @@ public sealed class AnyTimeOnly : IAny, IHasRandomSource, ICardinality
#region Statics members declarations
internal static AnyTimeOnly Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyTimeOnly(source, OrdinalIntervalSpec.Unconstrained("TimeOnly", ordinal => V(Val(ordinal)), Ord(TimeOnly.MinValue), Ord(TimeOnly.MaxValue)));
}
diff --git a/JustDummies/AnyTimeSpan.cs b/JustDummies/AnyTimeSpan.cs
index 1514fba7..f5460701 100644
--- a/JustDummies/AnyTimeSpan.cs
+++ b/JustDummies/AnyTimeSpan.cs
@@ -18,6 +18,8 @@ public sealed class AnyTimeSpan : IAny, IHasRandomSource, ICardinality
#region Statics members declarations
internal static AnyTimeSpan Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyTimeSpan(source, OrdinalIntervalSpec.Unconstrained("TimeSpan", ordinal => V(Val(ordinal)), Ord(TimeSpan.MinValue), Ord(TimeSpan.MaxValue)));
}
diff --git a/JustDummies/AnyUInt128.cs b/JustDummies/AnyUInt128.cs
index 7aa25df5..f3c32af0 100644
--- a/JustDummies/AnyUInt128.cs
+++ b/JustDummies/AnyUInt128.cs
@@ -19,6 +19,8 @@ public sealed class AnyUInt128 : IAny, IHasRandomSource, ICardinalityHi
#region Statics members declarations
internal static AnyUInt128 Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyUInt128(source, WideIntervalSpec.Unconstrained("UInt128", ordinal => V(Val(ordinal)), Ord(UInt128.MinValue), Ord(UInt128.MaxValue)));
}
diff --git a/JustDummies/AnyUInt16.cs b/JustDummies/AnyUInt16.cs
index a31e59d7..8e3de031 100644
--- a/JustDummies/AnyUInt16.cs
+++ b/JustDummies/AnyUInt16.cs
@@ -17,6 +17,8 @@ public sealed class AnyUInt16 : IAny, IHasRandomSource, ICardinalityHint
#region Statics members declarations
internal static AnyUInt16 Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyUInt16(source, OrdinalIntervalSpec.Unconstrained("UInt16", ordinal => V(Val(ordinal)), Ord(ushort.MinValue), Ord(ushort.MaxValue)));
}
diff --git a/JustDummies/AnyUInt32.cs b/JustDummies/AnyUInt32.cs
index 2cd9c84b..20397e4e 100644
--- a/JustDummies/AnyUInt32.cs
+++ b/JustDummies/AnyUInt32.cs
@@ -17,6 +17,8 @@ public sealed class AnyUInt32 : IAny, IHasRandomSource, ICardinalityHint V(Val(ordinal)), Ord(uint.MinValue), Ord(uint.MaxValue)));
}
diff --git a/JustDummies/AnyUInt64.cs b/JustDummies/AnyUInt64.cs
index 20a25a15..010c19f5 100644
--- a/JustDummies/AnyUInt64.cs
+++ b/JustDummies/AnyUInt64.cs
@@ -17,6 +17,8 @@ public sealed class AnyUInt64 : IAny, IHasRandomSource, ICardinalityHint<
#region Statics members declarations
internal static AnyUInt64 Create(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
return new AnyUInt64(source, OrdinalIntervalSpec.Unconstrained("UInt64", ordinal => V(Val(ordinal)), Ord(ulong.MinValue), Ord(ulong.MaxValue)));
}
diff --git a/JustDummies/AnyUri.cs b/JustDummies/AnyUri.cs
index a712cb74..ab576cc6 100644
--- a/JustDummies/AnyUri.cs
+++ b/JustDummies/AnyUri.cs
@@ -35,6 +35,8 @@ public sealed class AnyUri : IAny, IHasRandomSource {
#endregion
internal AnyUri(RandomSource source, UriSpec spec) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (spec is null) { throw new ArgumentNullException(nameof(spec)); }
_source = source;
_spec = spec;
}
diff --git a/JustDummies/AnyWebSocketUri.cs b/JustDummies/AnyWebSocketUri.cs
index 011f0950..dabb5653 100644
--- a/JustDummies/AnyWebSocketUri.cs
+++ b/JustDummies/AnyWebSocketUri.cs
@@ -15,6 +15,8 @@ public sealed class AnyWebSocketUri : IAny, IHasRandomSource {
#endregion
internal AnyWebSocketUri(RandomSource source, UriSpec spec) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (spec is null) { throw new ArgumentNullException(nameof(spec)); }
_source = source;
_spec = spec;
}
diff --git a/JustDummies/AnyWebUri.cs b/JustDummies/AnyWebUri.cs
index 82d3ed5b..32cf8658 100644
--- a/JustDummies/AnyWebUri.cs
+++ b/JustDummies/AnyWebUri.cs
@@ -16,6 +16,8 @@ public sealed class AnyWebUri : IAny, IHasRandomSource {
#endregion
internal AnyWebUri(RandomSource source, UriSpec spec) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+ if (spec is null) { throw new ArgumentNullException(nameof(spec)); }
_source = source;
_spec = spec;
}
diff --git a/JustDummies/CollectionState.cs b/JustDummies/CollectionState.cs
index 5284aa1f..56ff34e6 100644
--- a/JustDummies/CollectionState.cs
+++ b/JustDummies/CollectionState.cs
@@ -29,6 +29,8 @@ internal sealed class CollectionState {
#region Statics members declarations
internal static CollectionState Create(IAny item, bool distinct, IEqualityComparer? comparer) {
+ if (item is null) { throw new ArgumentNullException(nameof(item)); }
+
return new CollectionState(item, AnyDerivation.CardinalityOf(item), CountSpec.Unconstrained, distinct, comparer,
Array.Empty(), Array.Empty>());
}
@@ -83,36 +85,51 @@ private CollectionState(IAny item, long? itemCardinality, CountSpec count, bo
/// Fixes the exact element count.
internal CollectionState WithExactCount(int count, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
return Rebuild(_count.WithExactCount(count, applying), _distinct, _comparer, _fixedContaining, _generatedContaining, applying);
}
/// Tightens the minimum element count.
internal CollectionState WithMinCount(int count, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
return Rebuild(_count.WithMinCount(count, applying), _distinct, _comparer, _fixedContaining, _generatedContaining, applying);
}
/// Tightens the maximum element count.
internal CollectionState WithMaxCount(int count, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
return Rebuild(_count.WithMaxCount(count, applying), _distinct, _comparer, _fixedContaining, _generatedContaining, applying);
}
/// Requires the elements to be pairwise distinct, optionally under .
internal CollectionState AsDistinct(IEqualityComparer? comparer, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
return Rebuild(_count, true, comparer ?? _comparer, _fixedContaining, _generatedContaining, applying);
}
/// Requires the collection to contain .
internal CollectionState WithContaining(T value, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
return Rebuild(_count, _distinct, _comparer, Append(_fixedContaining, value), _generatedContaining, applying);
}
/// Requires the collection to contain a value drawn from .
internal CollectionState WithContaining(IAny generator, string applying) {
+ if (generator is null) { throw new ArgumentNullException(nameof(generator)); }
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
return Rebuild(_count, _distinct, _comparer, _fixedContaining, Append(_generatedContaining, generator), applying);
}
/// Builds one collection satisfying the whole specification — laid out directly, never generate-then-retry.
internal List Materialize(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
SeededRandom random = source.Current;
int required = _fixedContaining.Count + _generatedContaining.Count;
int? cap = _distinct ? CardinalityCap() : null;
diff --git a/JustDummies/ContinuousIntervalSpec.cs b/JustDummies/ContinuousIntervalSpec.cs
index 13a51048..b1ffff70 100644
--- a/JustDummies/ContinuousIntervalSpec.cs
+++ b/JustDummies/ContinuousIntervalSpec.cs
@@ -28,11 +28,17 @@ internal sealed class ContinuousIntervalSpec {
#region Statics members declarations
internal static ContinuousIntervalSpec Unconstrained(string typeName, Func render, Func quantize, Func nextUp, double domainMin, double domainMax) {
+ if (typeName is null) { throw new ArgumentNullException(nameof(typeName)); }
+ if (render is null) { throw new ArgumentNullException(nameof(render)); }
+ if (quantize is null) { throw new ArgumentNullException(nameof(quantize)); }
+ if (nextUp is null) { throw new ArgumentNullException(nameof(nextUp)); }
+
return new ContinuousIntervalSpec(typeName, render, quantize, nextUp, domainMin, null, domainMax, null, null, null, []);
}
/// Rejects NaN and the infinities — the shared argument guard of every floating-point generator.
internal static void EnsureFinite(double value, string parameterName) {
+ if (parameterName is null) { throw new ArgumentNullException(nameof(parameterName)); }
if (double.IsNaN(value) || double.IsInfinity(value)) { throw new ArgumentException("The value must be finite: NaN and infinities are never generated.", parameterName); }
}
@@ -93,6 +99,7 @@ private ContinuousIntervalSpec(string typeName, Func render, Fu
/// Tightens the lower bound; a looser bound than the current one is a no-op.
internal ContinuousIntervalSpec WithMinimum(double minimum, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
if (double.IsInfinity(minimum)) { throw new ConflictingAnyConstraintException($"Cannot apply {applying} because no {_typeName} value satisfies it."); }
if (minimum <= _min) { return this; }
@@ -107,6 +114,7 @@ internal ContinuousIntervalSpec WithMinimum(double minimum, string applying) {
/// Tightens the upper bound; a looser bound than the current one is a no-op.
internal ContinuousIntervalSpec WithMaximum(double maximum, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
if (double.IsInfinity(maximum)) { throw new ConflictingAnyConstraintException($"Cannot apply {applying} because no {_typeName} value satisfies it."); }
if (maximum >= _max) { return this; }
@@ -121,16 +129,22 @@ internal ContinuousIntervalSpec WithMaximum(double maximum, string applying) {
/// Tightens the lower bound to strictly above — via the type's next representable value.
internal ContinuousIntervalSpec WithMinimumAbove(double bound, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
return WithMinimum(_nextUp(bound), applying);
}
/// Tightens the upper bound to strictly below — via the type's next representable value.
internal ContinuousIntervalSpec WithMaximumBelow(double bound, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
return WithMaximum(-_nextUp(-bound), applying);
}
/// Restricts the domain to an explicit allow-list; declared once per generator.
internal ContinuousIntervalSpec WithAllowed(double[] values, string applying) {
+ if (values is null) { throw new ArgumentNullException(nameof(values)); }
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
if (_allowedConstraint is not null) { throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {_allowedConstraint} is already defined."); }
double[] distinct = values.Distinct().ToArray();
@@ -140,6 +154,9 @@ internal ContinuousIntervalSpec WithAllowed(double[] values, string applying) {
/// Adds values the generator must never produce.
internal ContinuousIntervalSpec WithExcluded(double[] values, string applying) {
+ if (values is null) { throw new ArgumentNullException(nameof(values)); }
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
// The applied constraint tags its own values, so a later exhaustion message can name the exclusion
// that actually emptied the domain rather than a bound that merely happens to border it.
List<(string Constraint, double[] Ordinals)> exclusions = new(_exclusions) { (applying, values) };
@@ -176,6 +193,8 @@ internal bool Contains(double value) {
/// Draws one value satisfying the whole specification.
internal double Generate(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
SeededRandom random = source.Current;
if (_effectiveAllowed is not null) {
diff --git a/JustDummies/CountConstraints.cs b/JustDummies/CountConstraints.cs
index c17aa1c6..817678a0 100644
--- a/JustDummies/CountConstraints.cs
+++ b/JustDummies/CountConstraints.cs
@@ -39,16 +39,21 @@ private static int RequireNonNegative(int count, string parameterName) {
/// Requires at least one element.
internal static CollectionState NonEmpty(CollectionState state) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
+
return state.WithMinCount(1, "NonEmpty()");
}
/// Fixes the collection to no elements.
internal static CollectionState Empty(CollectionState state) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
+
return state.WithExactCount(0, "Empty()");
}
/// Fixes the exact number of elements.
internal static CollectionState WithCount(CollectionState state, int count) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
RequireNonNegative(count, nameof(count));
return state.WithExactCount(count, $"WithCount({V(count)})");
@@ -56,6 +61,7 @@ internal static CollectionState WithCount(CollectionState state, int co
/// Requires at least elements.
internal static CollectionState WithMinCount(CollectionState state, int count) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
RequireNonNegative(count, nameof(count));
return state.WithMinCount(count, $"WithMinCount({V(count)})");
@@ -63,6 +69,7 @@ internal static CollectionState WithMinCount(CollectionState state, int
/// Requires at most elements.
internal static CollectionState WithMaxCount(CollectionState state, int count) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
RequireNonNegative(count, nameof(count));
return state.WithMaxCount(count, $"WithMaxCount({V(count)})");
@@ -70,6 +77,7 @@ internal static CollectionState WithMaxCount(CollectionState state, int
/// Requires a number of elements within the inclusive range [ , ].
internal static CollectionState WithCountBetween(CollectionState state, int minimum, int maximum) {
+ if (state is null) { throw new ArgumentNullException(nameof(state)); }
RequireNonNegative(minimum, nameof(minimum));
RequireNonNegative(maximum, nameof(maximum));
if (minimum > maximum) { throw new ArgumentException($"The minimum ({V(minimum)}) must be less than or equal to the maximum ({V(maximum)}).", nameof(minimum)); }
diff --git a/JustDummies/CountSpec.cs b/JustDummies/CountSpec.cs
index dc7fa1a2..dfb01ac2 100644
--- a/JustDummies/CountSpec.cs
+++ b/JustDummies/CountSpec.cs
@@ -69,6 +69,7 @@ private CountSpec(int? exact, string? exactConstraint,
/// Fixes the exact count; declared once per generator.
internal CountSpec WithExactCount(int count, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
if (_exactConstraint is not null) { throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {_exactConstraint} is already defined."); }
return new CountSpec(count, applying, _min, _minConstraint, _max, _maxConstraint).Validated(applying);
@@ -76,6 +77,7 @@ internal CountSpec WithExactCount(int count, string applying) {
/// Tightens the minimum count; a looser bound than the current one is a no-op.
internal CountSpec WithMinCount(int count, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
if (count <= _min) { return this; }
return new CountSpec(_exact, _exactConstraint, count, applying, _max, _maxConstraint).Validated(applying);
@@ -83,6 +85,7 @@ internal CountSpec WithMinCount(int count, string applying) {
/// Tightens the maximum count; a looser bound than the current one is a no-op.
internal CountSpec WithMaxCount(int count, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
if (_max is not null && count >= _max) { return this; }
return new CountSpec(_exact, _exactConstraint, _min, _minConstraint, count, applying).Validated(applying);
@@ -95,6 +98,7 @@ internal CountSpec WithMaxCount(int count, string applying) {
/// compatible with the declared bounds — the collection validates them eagerly before generation.
///
internal int Resolve(SeededRandom random, int requiredMin, int? cap) {
+ if (random is null) { throw new ArgumentNullException(nameof(random)); }
if (_exact is int exact) { return exact; }
int min = Math.Max(_min, requiredMin);
@@ -112,6 +116,7 @@ internal int Resolve(SeededRandom random, int requiredMin, int? cap) {
/// was the count cap or the containment requirement.
///
internal void EnsureFits(int required, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
int? cap = _exact ?? _max;
if (cap is int ceiling && required > ceiling) {
throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {Elements(required)} required to be contained cannot fit in a collection of at most {Elements(ceiling)}.");
diff --git a/JustDummies/DecimalIntervalSpec.cs b/JustDummies/DecimalIntervalSpec.cs
index a528e095..e5472a05 100644
--- a/JustDummies/DecimalIntervalSpec.cs
+++ b/JustDummies/DecimalIntervalSpec.cs
@@ -20,6 +20,9 @@ internal sealed class DecimalIntervalSpec {
#region Statics members declarations
internal static DecimalIntervalSpec Unconstrained(string typeName, Func render) {
+ if (typeName is null) { throw new ArgumentNullException(nameof(typeName)); }
+ if (render is null) { throw new ArgumentNullException(nameof(render)); }
+
return new DecimalIntervalSpec(typeName, render, decimal.MinValue, null, decimal.MaxValue, null, null, null, [], NoScale, null);
}
@@ -96,6 +99,7 @@ private DecimalIntervalSpec(string typeName, Func render,
/// Tightens the lower bound; a looser bound than the current one is a no-op.
internal DecimalIntervalSpec WithMinimum(decimal minimum, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
if (minimum <= _min) { return this; }
if (minimum > _max) {
@@ -109,6 +113,7 @@ internal DecimalIntervalSpec WithMinimum(decimal minimum, string applying) {
/// Tightens the upper bound; a looser bound than the current one is a no-op.
internal DecimalIntervalSpec WithMaximum(decimal maximum, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
if (maximum >= _max) { return this; }
if (maximum < _min) {
@@ -122,16 +127,22 @@ internal DecimalIntervalSpec WithMaximum(decimal maximum, string applying) {
/// Tightens the lower bound to strictly above — the inclusive bound plus a point exclusion.
internal DecimalIntervalSpec WithMinimumAbove(decimal bound, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
return WithMinimum(bound, applying).WithExcluded([bound], applying);
}
/// Tightens the upper bound to strictly below — the inclusive bound plus a point exclusion.
internal DecimalIntervalSpec WithMaximumBelow(decimal bound, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
return WithMaximum(bound, applying).WithExcluded([bound], applying);
}
/// Restricts the domain to an explicit allow-list; declared once per generator.
internal DecimalIntervalSpec WithAllowed(decimal[] values, string applying) {
+ if (values is null) { throw new ArgumentNullException(nameof(values)); }
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
if (_allowedConstraint is not null) { throw new ConflictingAnyConstraintException($"Cannot apply {applying} because {_allowedConstraint} is already defined."); }
decimal[] distinct = values.Distinct().ToArray();
@@ -141,6 +152,9 @@ internal DecimalIntervalSpec WithAllowed(decimal[] values, string applying) {
/// Adds values the generator must never produce.
internal DecimalIntervalSpec WithExcluded(decimal[] values, string applying) {
+ if (values is null) { throw new ArgumentNullException(nameof(values)); }
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
+
// The applied constraint tags its own values, so a later exhaustion message can name the exclusion
// that actually emptied the domain rather than a bound that merely happens to border it.
List<(string Constraint, decimal[] Ordinals)> exclusions = new(_exclusions) { (applying, values) };
@@ -154,6 +168,7 @@ internal DecimalIntervalSpec WithExcluded(decimal[] values, string applying) {
/// rendered form is not padded with trailing zeros. Declared once per generator.
///
internal DecimalIntervalSpec WithScale(int scale, string applying) {
+ if (applying is null) { throw new ArgumentNullException(nameof(applying)); }
if (_scale >= 0) {
if (_scale == scale) { return this; }
@@ -198,6 +213,8 @@ internal bool Contains(decimal value) {
/// Draws one value satisfying the whole specification.
internal decimal Generate(RandomSource source) {
+ if (source is null) { throw new ArgumentNullException(nameof(source)); }
+
SeededRandom random = source.Current;
if (_effectiveAllowed is not null) {
diff --git a/JustDummies/JustDummies.csproj b/JustDummies/JustDummies.csproj
index 3cf890e1..986cf99c 100644
--- a/JustDummies/JustDummies.csproj
+++ b/JustDummies/JustDummies.csproj
@@ -57,6 +57,14 @@
+
+
+
+
+