Skip to content

Commit 1fde9e5

Browse files
committed
Allow LINQ optimizations for FirstOrNone/LastOrNone
1 parent ac0dde1 commit 1fde9e5

File tree

6 files changed

+60
-3
lines changed

6 files changed

+60
-3
lines changed

Funcky.Test/Extensions/EnumerableExtensions/FirstSingleLastOrNoneTest.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma warning disable SA1010 // StyleCop support for collection expressions is missing
22
using System.Collections;
3+
using Funcky.Test.TestUtils;
4+
using Xunit.Sdk;
35

46
namespace Funcky.Test.Extensions.EnumerableExtensions;
57

@@ -24,6 +26,17 @@ public void GivenAnEnumerableSingleOrNoneGivesTheCorrectOption(List<int> valueEn
2426
ExpectedSingleOrNoneBehaviour(valueEnumerable, () => referenceEnumerable.SingleOrNone().Match(none: false, some: True));
2527
}
2628

29+
[Fact]
30+
public void DoesNotEnumerateListsWhenCalledWithoutPredicate()
31+
{
32+
var list = new FailOnEnumerateListWrapper<string>(["foo"]);
33+
_ = list.FirstOrNone();
34+
_ = list.LastOrNone();
35+
36+
// SingleOrNone does not specialize for `Select`ed lists.
37+
Assert.Throws<XunitException>(() => _ = list.SingleOrNone());
38+
}
39+
2740
public static TheoryData<List<int>, List<string>> ValueReferenceEnumerables()
2841
=> new()
2942
{
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Collections;
2+
using Xunit.Sdk;
3+
4+
namespace Funcky.Test.TestUtils;
5+
6+
internal class FailOnEnumerateCollectionWrapper<T>(ICollection<T> collection) : ICollection<T>
7+
{
8+
public bool IsReadOnly => true;
9+
10+
public int Count => collection.Count;
11+
12+
public IEnumerator<T> GetEnumerator() => throw new XunitException("Should not be enumerated");
13+
14+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
15+
16+
public void Add(T item) => throw new NotSupportedException();
17+
18+
public void Clear() => throw new NotSupportedException();
19+
20+
public bool Contains(T item) => collection.Contains(item);
21+
22+
public void CopyTo(T[] array, int arrayIndex) => collection.CopyTo(array, arrayIndex);
23+
24+
public bool Remove(T item) => throw new NotSupportedException();
25+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Funcky.Test.TestUtils;
2+
3+
internal sealed class FailOnEnumerateListWrapper<T>(IList<T> list) : FailOnEnumerateCollectionWrapper<T>(list), IList<T>
4+
{
5+
public T this[int index]
6+
{
7+
get => list[index];
8+
set => throw new NotSupportedException();
9+
}
10+
11+
public int IndexOf(T item) => list.IndexOf(item);
12+
13+
public void Insert(int index, T item) => throw new NotSupportedException();
14+
15+
public void RemoveAt(int index) => throw new NotSupportedException();
16+
}

Funcky/Extensions/EnumerableExtensions/FirstOrNone.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ public static partial class EnumerableExtensions
99
[Pure]
1010
public static Option<TSource> FirstOrNone<TSource>(this IEnumerable<TSource> source)
1111
where TSource : notnull
12-
=> source.FirstOrNone(True);
12+
=> source
13+
.Select(Option.Some)
14+
.FirstOrDefault();
1315

1416
/// <summary>
1517
/// Returns the first element of the sequence as an <see cref="Option{T}" /> that satisfies a condition or a <see cref="Option{T}.None" /> value if no such element is found.

Funcky/Extensions/EnumerableExtensions/LastOrNone.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ public static partial class EnumerableExtensions
99
[Pure]
1010
public static Option<TSource> LastOrNone<TSource>(this IEnumerable<TSource> source)
1111
where TSource : notnull
12-
=> source.LastOrNone(True);
12+
=> source
13+
.Select(Option.Some)
14+
.LastOrDefault();
1315

1416
/// <summary>
1517
/// Returns the last element of a sequence that satisfies a condition as an <see cref="Option{T}" /> or a <see cref="Option{T}.None" /> value if no such element is found.

Funcky/Monads/Option/OptionExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.ComponentModel;
22

3-
#pragma warning disable RS0026
43
namespace Funcky.Monads;
54

65
public static partial class OptionExtensions

0 commit comments

Comments
 (0)