diff --git a/FrameworkFeatureConstants.props b/FrameworkFeatureConstants.props index 819d8f1d..6912a671 100644 --- a/FrameworkFeatureConstants.props +++ b/FrameworkFeatureConstants.props @@ -4,7 +4,7 @@ $(DefineConstants);INDEX_OF_CHAR_COMPARISONTYPE_SUPPORTED;TIMESPAN_MULTIPLY_SUPPORTED;SPLIT_ACCEPTS_STRING_SEPARATOR;LAZY_RETURN_CONSTRUCTOR;QUEUE_TRY_OVERLOADS;OPTIMIZED_ELEMENT_AT;RANGE_SUPPORTED;READ_ONLY_SPAN_SUPPORTED;INDEX_TYPE;JOIN_TO_STRING_CHAR_SEPARATOR;REMOVE_EXTENSION - $(DefineConstants);SYSTEM_INDEX_SUPPORTED;IP_END_POINT_TRY_PARSE_SUPPORTED + $(DefineConstants);SYSTEM_INDEX_SUPPORTED;IP_END_POINT_TRY_PARSE_SUPPORTED;OPTIMIZED_FIRST_LAST_OR_DEFAULT $(DefineConstants);SET_CURRENT_STACK_TRACE_SUPPORTED;DYNAMICALLY_ACCESSED_MEMBERS_ATTRIBUTE_SUPPORTED diff --git a/Funcky.Test/Extensions/EnumerableExtensions/FirstSingleLastOrNoneTest.cs b/Funcky.Test/Extensions/EnumerableExtensions/FirstSingleLastOrNoneTest.cs index 3a526666..ce90a8ab 100644 --- a/Funcky.Test/Extensions/EnumerableExtensions/FirstSingleLastOrNoneTest.cs +++ b/Funcky.Test/Extensions/EnumerableExtensions/FirstSingleLastOrNoneTest.cs @@ -1,5 +1,7 @@ #pragma warning disable SA1010 // StyleCop support for collection expressions is missing using System.Collections; +using Funcky.Test.TestUtils; +using Xunit.Sdk; namespace Funcky.Test.Extensions.EnumerableExtensions; @@ -24,6 +26,21 @@ public void GivenAnEnumerableSingleOrNoneGivesTheCorrectOption(List valueEn ExpectedSingleOrNoneBehaviour(valueEnumerable, () => referenceEnumerable.SingleOrNone().Match(none: false, some: True)); } +#if OPTIMIZED_FIRST_LAST_OR_DEFAULT + [Fact] +#else + [Fact(Skip = ".NET Framework 4.8 doesn't optimize these methods")] +#endif + public void DoesNotEnumerateListsWhenCalledWithoutPredicate() + { + var list = new FailOnEnumerateListWrapper(["foo"]); + _ = list.FirstOrNone(); + _ = list.LastOrNone(); + + // SingleOrNone does not specialize for `Select`ed lists. + Assert.Throws(() => _ = list.SingleOrNone()); + } + public static TheoryData, List> ValueReferenceEnumerables() => new() { diff --git a/Funcky.Test/TestUtils/FailOnEnumerateCollectionWrapper.cs b/Funcky.Test/TestUtils/FailOnEnumerateCollectionWrapper.cs new file mode 100644 index 00000000..d829d0e2 --- /dev/null +++ b/Funcky.Test/TestUtils/FailOnEnumerateCollectionWrapper.cs @@ -0,0 +1,25 @@ +using System.Collections; +using Xunit.Sdk; + +namespace Funcky.Test.TestUtils; + +internal class FailOnEnumerateCollectionWrapper(ICollection collection) : ICollection +{ + public bool IsReadOnly => true; + + public int Count => collection.Count; + + public IEnumerator GetEnumerator() => throw new XunitException("Should not be enumerated"); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public void Add(T item) => throw new NotSupportedException(); + + public void Clear() => throw new NotSupportedException(); + + public bool Contains(T item) => collection.Contains(item); + + public void CopyTo(T[] array, int arrayIndex) => collection.CopyTo(array, arrayIndex); + + public bool Remove(T item) => throw new NotSupportedException(); +} diff --git a/Funcky.Test/TestUtils/FailOnEnumerateListWrapper.cs b/Funcky.Test/TestUtils/FailOnEnumerateListWrapper.cs new file mode 100644 index 00000000..af6edefd --- /dev/null +++ b/Funcky.Test/TestUtils/FailOnEnumerateListWrapper.cs @@ -0,0 +1,16 @@ +namespace Funcky.Test.TestUtils; + +internal sealed class FailOnEnumerateListWrapper(IList list) : FailOnEnumerateCollectionWrapper(list), IList +{ + public T this[int index] + { + get => list[index]; + set => throw new NotSupportedException(); + } + + public int IndexOf(T item) => list.IndexOf(item); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + public void RemoveAt(int index) => throw new NotSupportedException(); +} diff --git a/Funcky/Extensions/EnumerableExtensions/FirstOrNone.cs b/Funcky/Extensions/EnumerableExtensions/FirstOrNone.cs index 9515da05..db71ebd2 100644 --- a/Funcky/Extensions/EnumerableExtensions/FirstOrNone.cs +++ b/Funcky/Extensions/EnumerableExtensions/FirstOrNone.cs @@ -9,7 +9,9 @@ public static partial class EnumerableExtensions [Pure] public static Option FirstOrNone(this IEnumerable source) where TSource : notnull - => source.FirstOrNone(True); + => source + .Select(Option.Some) + .FirstOrDefault(); /// /// Returns the first element of the sequence as an that satisfies a condition or a value if no such element is found. diff --git a/Funcky/Extensions/EnumerableExtensions/LastOrNone.cs b/Funcky/Extensions/EnumerableExtensions/LastOrNone.cs index bb3f5b24..5899db01 100644 --- a/Funcky/Extensions/EnumerableExtensions/LastOrNone.cs +++ b/Funcky/Extensions/EnumerableExtensions/LastOrNone.cs @@ -9,7 +9,9 @@ public static partial class EnumerableExtensions [Pure] public static Option LastOrNone(this IEnumerable source) where TSource : notnull - => source.LastOrNone(True); + => source + .Select(Option.Some) + .LastOrDefault(); /// /// Returns the last element of a sequence that satisfies a condition as an or a value if no such element is found. diff --git a/Funcky/Monads/Option/OptionExtensions.cs b/Funcky/Monads/Option/OptionExtensions.cs index 35e0a661..0e516c64 100644 --- a/Funcky/Monads/Option/OptionExtensions.cs +++ b/Funcky/Monads/Option/OptionExtensions.cs @@ -1,6 +1,5 @@ using System.ComponentModel; -#pragma warning disable RS0026 namespace Funcky.Monads; public static partial class OptionExtensions