Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions Dummies.UnitTests/CompositionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,43 @@ public void CombineWrapsComposerFailures() {
Check.That(caught.InnerException).IsInstanceOf<InvalidOperationException>();
}

[Fact(DisplayName = "Combine composes four through eight parts, passing every constrained part to the lambda.")]
public void CombineSupportsHigherArities() {
IAny<int> part = Any.Int32().Between(1, 9);

for (int i = 0; i < 50; i++) {
int[] four = Any.Combine(part, part, part, part, (a, b, c, d) => new[] { a, b, c, d }).Generate();
int[] five = Any.Combine(part, part, part, part, part, (a, b, c, d, e) => new[] { a, b, c, d, e }).Generate();
int[] six = Any.Combine(part, part, part, part, part, part, (a, b, c, d, e, f) => new[] { a, b, c, d, e, f }).Generate();
int[] seven = Any.Combine(part, part, part, part, part, part, part, (a, b, c, d, e, f, g) => new[] { a, b, c, d, e, f, g }).Generate();
int[] eight = Any.Combine(part, part, part, part, part, part, part, part, (a, b, c, d, e, f, g, h) => new[] { a, b, c, d, e, f, g, h }).Generate();

Check.That(four.Length).IsEqualTo(4);
Check.That(five.Length).IsEqualTo(5);
Check.That(six.Length).IsEqualTo(6);
Check.That(seven.Length).IsEqualTo(7);
Check.That(eight.Length).IsEqualTo(8);
foreach (int[] parts in new[] { four, five, six, seven, eight }) {
Check.That(parts).ContainsOnlyElementsThatMatch(value => value is >= 1 and <= 9);
}
}
}

[Fact(DisplayName = "A higher-arity Combine validates its arguments and wraps composer failures.")]
public void HigherArityCombineValidatesAndWraps() {
Check.ThatCode(() => Any.Combine(Any.Int32(), Any.Int32(), Any.Int32(), Any.Int32(), (Func<int, int, int, int, int>)null!)).Throws<ArgumentNullException>();
Check.ThatCode(() => Any.Combine<int, int, int, int, int>(Any.Int32(), Any.Int32(), Any.Int32(), null!, (a, b, c, d) => a)).Throws<ArgumentNullException>();

IAny<string> failing = Any.Combine<int, int, int, int, int, int, int, int, string>(
Any.Int32().Between(1, 2), Any.Int32().Between(1, 2), Any.Int32().Between(1, 2), Any.Int32().Between(1, 2),
Any.Int32().Between(1, 2), Any.Int32().Between(1, 2), Any.Int32().Between(1, 2), Any.Int32().Between(1, 2),
(a, b, c, d, e, f, g, h) => throw new InvalidOperationException("rejected"));

AnyGenerationException caught = Assert.Throws<AnyGenerationException>(() => failing.Generate());
Check.That(caught.Message).Contains("Combine(...)");
Check.That(caught.InnerException).IsInstanceOf<InvalidOperationException>();
}

[Fact(DisplayName = "Generic inference flows through IAny<T> without relying on implicit conversions.")]
public void GenericInferenceFlowsThroughIAny() {
string text = Materialize(Any.String().NonEmpty().WithMaxLength(50));
Expand Down
215 changes: 215 additions & 0 deletions Dummies/Any.cs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@
/// <typeparam name="TResult">The type of the composed value.</typeparam>
/// <returns>A generator of the composed value.</returns>
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
public static IAny<TResult> Combine<T1, T2, T3, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, Func<T1, T2, T3, TResult> compose) {

Check warning on line 416 in Dummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.

Check warning on line 416 in Dummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
Expand All @@ -430,6 +430,221 @@
});
}

/// <summary>
/// Composes four generators into one through a constructor lambda — see <see cref="Combine{T1,T2,TResult}" />.
/// </summary>
/// <param name="first">The generator of the first part.</param>
/// <param name="second">The generator of the second part.</param>
/// <param name="third">The generator of the third part.</param>
/// <param name="fourth">The generator of the fourth part.</param>
/// <param name="compose">The constructor lambda assembling the parts.</param>
/// <typeparam name="T1">The type of the first part.</typeparam>
/// <typeparam name="T2">The type of the second part.</typeparam>
/// <typeparam name="T3">The type of the third part.</typeparam>
/// <typeparam name="T4">The type of the fourth part.</typeparam>
/// <typeparam name="TResult">The type of the composed value.</typeparam>
/// <returns>A generator of the composed value.</returns>
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
public static IAny<TResult> Combine<T1, T2, T3, T4, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, IAny<T4> fourth, Func<T1, T2, T3, T4, TResult> compose) {

Check warning on line 448 in Dummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.

Check warning on line 448 in Dummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
if (fourth is null) { throw new ArgumentNullException(nameof(fourth)); }
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third) ?? AnyDerivation.SourceOf(fourth);

return new DerivedAny<TResult>(source, () => {
T1 firstValue = first.Generate();
T2 secondValue = second.Generate();
T3 thirdValue = third.Generate();
T4 fourthValue = fourth.Generate();

return AnyDerivation.Invoke(() => compose(firstValue, secondValue, thirdValue, fourthValue), source, $"the composer passed to Combine(...) threw for the generated values ({AnyDerivation.Display(firstValue)}, {AnyDerivation.Display(secondValue)}, {AnyDerivation.Display(thirdValue)}, {AnyDerivation.Display(fourthValue)})");
});
}

/// <summary>
/// Composes five generators into one through a constructor lambda — see <see cref="Combine{T1,T2,TResult}" />.
/// </summary>
/// <param name="first">The generator of the first part.</param>
/// <param name="second">The generator of the second part.</param>
/// <param name="third">The generator of the third part.</param>
/// <param name="fourth">The generator of the fourth part.</param>
/// <param name="fifth">The generator of the fifth part.</param>
/// <param name="compose">The constructor lambda assembling the parts.</param>
/// <typeparam name="T1">The type of the first part.</typeparam>
/// <typeparam name="T2">The type of the second part.</typeparam>
/// <typeparam name="T3">The type of the third part.</typeparam>
/// <typeparam name="T4">The type of the fourth part.</typeparam>
/// <typeparam name="T5">The type of the fifth part.</typeparam>
/// <typeparam name="TResult">The type of the composed value.</typeparam>
/// <returns>A generator of the composed value.</returns>
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
public static IAny<TResult> Combine<T1, T2, T3, T4, T5, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, IAny<T4> fourth, IAny<T5> fifth, Func<T1, T2, T3, T4, T5, TResult> compose) {

Check warning on line 484 in Dummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.

Check warning on line 484 in Dummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
if (fourth is null) { throw new ArgumentNullException(nameof(fourth)); }
if (fifth is null) { throw new ArgumentNullException(nameof(fifth)); }
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third) ?? AnyDerivation.SourceOf(fourth) ?? AnyDerivation.SourceOf(fifth);

return new DerivedAny<TResult>(source, () => {
T1 firstValue = first.Generate();
T2 secondValue = second.Generate();
T3 thirdValue = third.Generate();
T4 fourthValue = fourth.Generate();
T5 fifthValue = fifth.Generate();

return AnyDerivation.Invoke(() => compose(firstValue, secondValue, thirdValue, fourthValue, fifthValue), source, $"the composer passed to Combine(...) threw for the generated values ({AnyDerivation.Display(firstValue)}, {AnyDerivation.Display(secondValue)}, {AnyDerivation.Display(thirdValue)}, {AnyDerivation.Display(fourthValue)}, {AnyDerivation.Display(fifthValue)})");
});
}

/// <summary>
/// Composes six generators into one through a constructor lambda — see <see cref="Combine{T1,T2,TResult}" />.
/// </summary>
/// <param name="first">The generator of the first part.</param>
/// <param name="second">The generator of the second part.</param>
/// <param name="third">The generator of the third part.</param>
/// <param name="fourth">The generator of the fourth part.</param>
/// <param name="fifth">The generator of the fifth part.</param>
/// <param name="sixth">The generator of the sixth part.</param>
/// <param name="compose">The constructor lambda assembling the parts.</param>
/// <typeparam name="T1">The type of the first part.</typeparam>
/// <typeparam name="T2">The type of the second part.</typeparam>
/// <typeparam name="T3">The type of the third part.</typeparam>
/// <typeparam name="T4">The type of the fourth part.</typeparam>
/// <typeparam name="T5">The type of the fifth part.</typeparam>
/// <typeparam name="T6">The type of the sixth part.</typeparam>
/// <typeparam name="TResult">The type of the composed value.</typeparam>
/// <returns>A generator of the composed value.</returns>
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
public static IAny<TResult> Combine<T1, T2, T3, T4, T5, T6, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, IAny<T4> fourth, IAny<T5> fifth, IAny<T6> sixth, Func<T1, T2, T3, T4, T5, T6, TResult> compose) {

Check warning on line 524 in Dummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.

Check warning on line 524 in Dummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
if (fourth is null) { throw new ArgumentNullException(nameof(fourth)); }
if (fifth is null) { throw new ArgumentNullException(nameof(fifth)); }
if (sixth is null) { throw new ArgumentNullException(nameof(sixth)); }
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third) ?? AnyDerivation.SourceOf(fourth) ?? AnyDerivation.SourceOf(fifth) ?? AnyDerivation.SourceOf(sixth);

return new DerivedAny<TResult>(source, () => {
T1 firstValue = first.Generate();
T2 secondValue = second.Generate();
T3 thirdValue = third.Generate();
T4 fourthValue = fourth.Generate();
T5 fifthValue = fifth.Generate();
T6 sixthValue = sixth.Generate();

return AnyDerivation.Invoke(() => compose(firstValue, secondValue, thirdValue, fourthValue, fifthValue, sixthValue), source, $"the composer passed to Combine(...) threw for the generated values ({AnyDerivation.Display(firstValue)}, {AnyDerivation.Display(secondValue)}, {AnyDerivation.Display(thirdValue)}, {AnyDerivation.Display(fourthValue)}, {AnyDerivation.Display(fifthValue)}, {AnyDerivation.Display(sixthValue)})");
});
}

/// <summary>
/// Composes seven generators into one through a constructor lambda — see <see cref="Combine{T1,T2,TResult}" />.
/// </summary>
/// <param name="first">The generator of the first part.</param>
/// <param name="second">The generator of the second part.</param>
/// <param name="third">The generator of the third part.</param>
/// <param name="fourth">The generator of the fourth part.</param>
/// <param name="fifth">The generator of the fifth part.</param>
/// <param name="sixth">The generator of the sixth part.</param>
/// <param name="seventh">The generator of the seventh part.</param>
/// <param name="compose">The constructor lambda assembling the parts.</param>
/// <typeparam name="T1">The type of the first part.</typeparam>
/// <typeparam name="T2">The type of the second part.</typeparam>
/// <typeparam name="T3">The type of the third part.</typeparam>
/// <typeparam name="T4">The type of the fourth part.</typeparam>
/// <typeparam name="T5">The type of the fifth part.</typeparam>
/// <typeparam name="T6">The type of the sixth part.</typeparam>
/// <typeparam name="T7">The type of the seventh part.</typeparam>
/// <typeparam name="TResult">The type of the composed value.</typeparam>
/// <returns>A generator of the composed value.</returns>
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters",
Justification = "Heterogeneous composition needs one generator parameter per part; the arity-8 ceiling is a deliberate ergonomic decision (ADR-0015), and a flat parameter list reads better at the call site than nested Combine calls.")]
public static IAny<TResult> Combine<T1, T2, T3, T4, T5, T6, T7, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, IAny<T4> fourth, IAny<T5> fifth, IAny<T6> sixth, IAny<T7> seventh, Func<T1, T2, T3, T4, T5, T6, T7, TResult> compose) {

Check warning on line 570 in Dummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
if (fourth is null) { throw new ArgumentNullException(nameof(fourth)); }
if (fifth is null) { throw new ArgumentNullException(nameof(fifth)); }
if (sixth is null) { throw new ArgumentNullException(nameof(sixth)); }
if (seventh is null) { throw new ArgumentNullException(nameof(seventh)); }
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third) ?? AnyDerivation.SourceOf(fourth) ?? AnyDerivation.SourceOf(fifth) ?? AnyDerivation.SourceOf(sixth) ?? AnyDerivation.SourceOf(seventh);

return new DerivedAny<TResult>(source, () => {
T1 firstValue = first.Generate();
T2 secondValue = second.Generate();
T3 thirdValue = third.Generate();
T4 fourthValue = fourth.Generate();
T5 fifthValue = fifth.Generate();
T6 sixthValue = sixth.Generate();
T7 seventhValue = seventh.Generate();

return AnyDerivation.Invoke(() => compose(firstValue, secondValue, thirdValue, fourthValue, fifthValue, sixthValue, seventhValue), source, $"the composer passed to Combine(...) threw for the generated values ({AnyDerivation.Display(firstValue)}, {AnyDerivation.Display(secondValue)}, {AnyDerivation.Display(thirdValue)}, {AnyDerivation.Display(fourthValue)}, {AnyDerivation.Display(fifthValue)}, {AnyDerivation.Display(sixthValue)}, {AnyDerivation.Display(seventhValue)})");
});
}

/// <summary>
/// Composes eight generators into one through a constructor lambda — see <see cref="Combine{T1,T2,TResult}" />.
/// Eight is the ceiling; a constructor needing more parts is better assembled from intermediate value objects.
/// </summary>
/// <param name="first">The generator of the first part.</param>
/// <param name="second">The generator of the second part.</param>
/// <param name="third">The generator of the third part.</param>
/// <param name="fourth">The generator of the fourth part.</param>
/// <param name="fifth">The generator of the fifth part.</param>
/// <param name="sixth">The generator of the sixth part.</param>
/// <param name="seventh">The generator of the seventh part.</param>
/// <param name="eighth">The generator of the eighth part.</param>
/// <param name="compose">The constructor lambda assembling the parts.</param>
/// <typeparam name="T1">The type of the first part.</typeparam>
/// <typeparam name="T2">The type of the second part.</typeparam>
/// <typeparam name="T3">The type of the third part.</typeparam>
/// <typeparam name="T4">The type of the fourth part.</typeparam>
/// <typeparam name="T5">The type of the fifth part.</typeparam>
/// <typeparam name="T6">The type of the sixth part.</typeparam>
/// <typeparam name="T7">The type of the seventh part.</typeparam>
/// <typeparam name="T8">The type of the eighth part.</typeparam>
/// <typeparam name="TResult">The type of the composed value.</typeparam>
/// <returns>A generator of the composed value.</returns>
/// <exception cref="ArgumentNullException">Thrown when any argument is <c>null</c>.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters",
Justification = "Heterogeneous composition needs one generator parameter per part; the arity-8 ceiling is a deliberate ergonomic decision (ADR-0015), and a flat parameter list reads better at the call site than nested Combine calls.")]
public static IAny<TResult> Combine<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(IAny<T1> first, IAny<T2> second, IAny<T3> third, IAny<T4> fourth, IAny<T5> fifth, IAny<T6> sixth, IAny<T7> seventh, IAny<T8> eighth, Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult> compose) {

Check warning on line 621 in Dummies/Any.cs

View workflow job for this annotation

GitHub Actions / SonarQube Cloud analysis

Reduce the number of generic parameters in the 'Any.Combine' method to no more than the 3 authorized.
if (first is null) { throw new ArgumentNullException(nameof(first)); }
if (second is null) { throw new ArgumentNullException(nameof(second)); }
if (third is null) { throw new ArgumentNullException(nameof(third)); }
if (fourth is null) { throw new ArgumentNullException(nameof(fourth)); }
if (fifth is null) { throw new ArgumentNullException(nameof(fifth)); }
if (sixth is null) { throw new ArgumentNullException(nameof(sixth)); }
if (seventh is null) { throw new ArgumentNullException(nameof(seventh)); }
if (eighth is null) { throw new ArgumentNullException(nameof(eighth)); }
if (compose is null) { throw new ArgumentNullException(nameof(compose)); }

RandomSource? source = AnyDerivation.SourceOf(first) ?? AnyDerivation.SourceOf(second) ?? AnyDerivation.SourceOf(third) ?? AnyDerivation.SourceOf(fourth) ?? AnyDerivation.SourceOf(fifth) ?? AnyDerivation.SourceOf(sixth) ?? AnyDerivation.SourceOf(seventh) ?? AnyDerivation.SourceOf(eighth);

return new DerivedAny<TResult>(source, () => {
T1 firstValue = first.Generate();
T2 secondValue = second.Generate();
T3 thirdValue = third.Generate();
T4 fourthValue = fourth.Generate();
T5 fifthValue = fifth.Generate();
T6 sixthValue = sixth.Generate();
T7 seventhValue = seventh.Generate();
T8 eighthValue = eighth.Generate();

return AnyDerivation.Invoke(() => compose(firstValue, secondValue, thirdValue, fourthValue, fifthValue, sixthValue, seventhValue, eighthValue), source, $"the composer passed to Combine(...) threw for the generated values ({AnyDerivation.Display(firstValue)}, {AnyDerivation.Display(secondValue)}, {AnyDerivation.Display(thirdValue)}, {AnyDerivation.Display(fourthValue)}, {AnyDerivation.Display(fifthValue)}, {AnyDerivation.Display(sixthValue)}, {AnyDerivation.Display(seventhValue)}, {AnyDerivation.Display(eighthValue)})");
});
}

/// <summary>
/// Starts an arbitrary <see cref="List{T}" /> generator over <paramref name="item" />. Unconstrained, it yields
/// 0 to 8 elements; chain constraints to express what the surrounding code requires (<c>NonEmpty()</c>,
Expand Down
Loading