From bb01dfae71bab856cf90f13cb65c86351becceeb Mon Sep 17 00:00:00 2001 From: Stuart Turner Date: Tue, 15 Nov 2022 15:47:59 -0600 Subject: [PATCH 1/3] Enable nullable context for "PartialSort" tests (#888) --- MoreLinq.Test/PartialSortByTest.cs | 2 ++ MoreLinq.Test/PartialSortTest.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/MoreLinq.Test/PartialSortByTest.cs b/MoreLinq.Test/PartialSortByTest.cs index 9c83dcd7c..7330a151c 100644 --- a/MoreLinq.Test/PartialSortByTest.cs +++ b/MoreLinq.Test/PartialSortByTest.cs @@ -15,6 +15,8 @@ // limitations under the License. #endregion +#nullable enable + namespace MoreLinq.Test { using System; diff --git a/MoreLinq.Test/PartialSortTest.cs b/MoreLinq.Test/PartialSortTest.cs index 04257a737..53ad4d729 100644 --- a/MoreLinq.Test/PartialSortTest.cs +++ b/MoreLinq.Test/PartialSortTest.cs @@ -15,6 +15,8 @@ // limitations under the License. #endregion +#nullable enable + namespace MoreLinq.Test { using System; From d58741681e4743cde3e56686c5c13ce2b1e34e15 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 16 Nov 2022 20:53:58 +0100 Subject: [PATCH 2/3] Fix null annotation in "Subject" (#891) --- MoreLinq/Reactive/Subject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MoreLinq/Reactive/Subject.cs b/MoreLinq/Reactive/Subject.cs index 2f01e8047..f1fc91a6a 100644 --- a/MoreLinq/Reactive/Subject.cs +++ b/MoreLinq/Reactive/Subject.cs @@ -116,12 +116,12 @@ public void OnNext(T value) } public void OnError(Exception error) => - OnFinality(ref _error, error, (observer, err) => observer.OnError(err!)); + OnFinality(ref _error, error, (observer, err) => observer.OnError(err)); public void OnCompleted() => OnFinality(ref _completed, true, (observer, _) => observer.OnCompleted()); - void OnFinality(ref TState state, TState value, Action, TState> action) + void OnFinality(ref TState? state, TState value, Action, TState> action) { if (IsMuted) return; From b31c7fdd6b02a77f37e57c0684e6bdcb8b51e5ba Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 16 Nov 2022 23:04:05 +0100 Subject: [PATCH 3/3] Replace null-forgiving operator uses with debug assertions (#890) --- MoreLinq.Test/FallbackIfEmptyTest.cs | 8 +++++++ MoreLinq/Assume.cs | 31 +++++++++++++++++++++++++++ MoreLinq/Debug.cs | 31 +++++++++++++++++++++++++++ MoreLinq/Experimental/Await.cs | 10 +++------ MoreLinq/Experimental/Memoize.cs | 4 +--- MoreLinq/FallbackIfEmpty.cs | 1 - MoreLinq/Fold.cs | 32 ++++++++++++++-------------- MoreLinq/Lookup.cs | 29 +++++++++++++------------ MoreLinq/Pad.cs | 1 - MoreLinq/PartialSort.cs | 2 +- MoreLinq/Partition.cs | 1 - 11 files changed, 106 insertions(+), 44 deletions(-) create mode 100644 MoreLinq/Assume.cs create mode 100644 MoreLinq/Debug.cs diff --git a/MoreLinq.Test/FallbackIfEmptyTest.cs b/MoreLinq.Test/FallbackIfEmptyTest.cs index b8e6b7516..cf6a2df83 100644 --- a/MoreLinq.Test/FallbackIfEmptyTest.cs +++ b/MoreLinq.Test/FallbackIfEmptyTest.cs @@ -60,5 +60,13 @@ public void FallbackIfEmptyPreservesFallbackCollectionIfPossible(SourceKind sour Assert.AreSame(source.FallbackIfEmpty(fallback), fallback); Assert.AreSame(source.FallbackIfEmpty(fallback.AsEnumerable()), fallback); } + + [Test] + public void FallbackIfEmptyWithEmptyNullableSequence() + { + var source = Enumerable.Empty().Select(x => x); + var fallback = (int?)null; + source.FallbackIfEmpty(fallback).AssertSequenceEqual(fallback); + } } } diff --git a/MoreLinq/Assume.cs b/MoreLinq/Assume.cs new file mode 100644 index 000000000..08b60162a --- /dev/null +++ b/MoreLinq/Assume.cs @@ -0,0 +1,31 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2022 Atif Aziz. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#endregion + +namespace MoreLinq +{ + using System.Runtime.CompilerServices; + + static class Assume + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T NotNull(T? obj) where T : class + { + Debug.Assert(obj is not null); + return obj; + } + } +} diff --git a/MoreLinq/Debug.cs b/MoreLinq/Debug.cs new file mode 100644 index 000000000..6b36c21c7 --- /dev/null +++ b/MoreLinq/Debug.cs @@ -0,0 +1,31 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2022 Atif Aziz. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#endregion + +namespace MoreLinq +{ + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.CompilerServices; + + static class Debug + { + [Conditional("DEBUG")] + public static void Assert([DoesNotReturnIf(false)] bool condition, + [CallerArgumentExpression(nameof(condition))] string? message = null) => + System.Diagnostics.Debug.Assert(condition); + } +} diff --git a/MoreLinq/Experimental/Await.cs b/MoreLinq/Experimental/Await.cs index 9b54d9e35..3873754ea 100644 --- a/MoreLinq/Experimental/Await.cs +++ b/MoreLinq/Experimental/Await.cs @@ -23,7 +23,6 @@ namespace MoreLinq.Experimental using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; - using System.Diagnostics; using System.Linq; using System.Runtime.ExceptionServices; using System.Threading; @@ -528,18 +527,15 @@ void PostNotice(Notice notice, catch (OperationCanceledException e) when (e.CancellationToken == consumerCancellationTokenSource.Token) { var (error1, error2) = lastCriticalErrors; - Debug.Assert(error1 is not null); throw new Exception("One or more critical errors have occurred.", - error2 != null ? new AggregateException(error1, error2) - : new AggregateException(error1)); + error2 != null ? new AggregateException(Assume.NotNull(error1), error2) + : new AggregateException(Assume.NotNull(error1))); } var (kind, result, error) = notice.Current; if (kind == Notice.Error) - { - error!.Throw(); - } + Assume.NotNull(error).Throw(); if (kind == Notice.End) break; diff --git a/MoreLinq/Experimental/Memoize.cs b/MoreLinq/Experimental/Memoize.cs index 0526cec4f..da261b1a2 100644 --- a/MoreLinq/Experimental/Memoize.cs +++ b/MoreLinq/Experimental/Memoize.cs @@ -115,9 +115,7 @@ public IEnumerator GetEnumerator() if (index >= _cache.Count) { if (index == _errorIndex) - { - _error!.Throw(); - } + Assume.NotNull(_error).Throw(); if (_sourceEnumerator == null) break; diff --git a/MoreLinq/FallbackIfEmpty.cs b/MoreLinq/FallbackIfEmpty.cs index 90256fcad..30a16726e 100644 --- a/MoreLinq/FallbackIfEmpty.cs +++ b/MoreLinq/FallbackIfEmpty.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; static partial class MoreEnumerable { diff --git a/MoreLinq/Fold.cs b/MoreLinq/Fold.cs index 6698cec9a..31fa1bbef 100644 --- a/MoreLinq/Fold.cs +++ b/MoreLinq/Fold.cs @@ -69,22 +69,22 @@ static TResult FoldImpl(IEnumerable source, int count, return count switch { - 1 => folder1 !(elements[0]), - 2 => folder2 !(elements[0], elements[1]), - 3 => folder3 !(elements[0], elements[1], elements[2]), - 4 => folder4 !(elements[0], elements[1], elements[2], elements[3]), - 5 => folder5 !(elements[0], elements[1], elements[2], elements[3], elements[4]), - 6 => folder6 !(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]), - 7 => folder7 !(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6]), - 8 => folder8 !(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7]), - 9 => folder9 !(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8]), - 10 => folder10!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9]), - 11 => folder11!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10]), - 12 => folder12!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11]), - 13 => folder13!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12]), - 14 => folder14!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13]), - 15 => folder15!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13], elements[14]), - 16 => folder16!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13], elements[14], elements[15]), + 1 => Assume.NotNull(folder1 )(elements[0]), + 2 => Assume.NotNull(folder2 )(elements[0], elements[1]), + 3 => Assume.NotNull(folder3 )(elements[0], elements[1], elements[2]), + 4 => Assume.NotNull(folder4 )(elements[0], elements[1], elements[2], elements[3]), + 5 => Assume.NotNull(folder5 )(elements[0], elements[1], elements[2], elements[3], elements[4]), + 6 => Assume.NotNull(folder6 )(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]), + 7 => Assume.NotNull(folder7 )(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6]), + 8 => Assume.NotNull(folder8 )(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7]), + 9 => Assume.NotNull(folder9 )(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8]), + 10 => Assume.NotNull(folder10)(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9]), + 11 => Assume.NotNull(folder11)(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10]), + 12 => Assume.NotNull(folder12)(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11]), + 13 => Assume.NotNull(folder13)(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12]), + 14 => Assume.NotNull(folder14)(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13]), + 15 => Assume.NotNull(folder15)(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13], elements[14]), + 16 => Assume.NotNull(folder16)(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13], elements[14], elements[15]), _ => throw new NotSupportedException() }; } diff --git a/MoreLinq/Lookup.cs b/MoreLinq/Lookup.cs index 8ee430a64..a25b104ec 100644 --- a/MoreLinq/Lookup.cs +++ b/MoreLinq/Lookup.cs @@ -24,12 +24,6 @@ // SOFTWARE. #endregion -#if !NET6_0_OR_GREATER -#nullable enable annotations -#pragma warning disable 8602 // Dereference of a possibly null reference. -#pragma warning disable 8603 // Possible null reference return. -#endif - namespace MoreLinq { using System; @@ -65,7 +59,10 @@ internal static Lookup Create(IEnumerable sour var lookup = new Lookup(comparer); foreach (var item in source) - lookup.GetGrouping(keySelector(item), create: true)!.Add(elementSelector(item)); + { + var grouping = Assume.NotNull(lookup.GetGrouping(keySelector(item), create: true)); + grouping.Add(elementSelector(item)); + } return lookup; } @@ -78,7 +75,10 @@ internal static Lookup Create(IEnumerable source, Func var lookup = new Lookup(comparer); foreach (var item in source) - lookup.GetGrouping(keySelector(item), create: true)!.Add(item); + { + var grouping = Assume.NotNull(lookup.GetGrouping(keySelector(item), create: true)); + grouping.Add(item); + } return lookup; } @@ -90,7 +90,10 @@ internal static Lookup CreateForJoin(IEnumerable sourc foreach (var item in source) { if (keySelector(item) is { } key) - lookup.GetGrouping(key, create: true)!.Add(item); + { + var grouping = Assume.NotNull(lookup.GetGrouping(key, create: true)); + grouping.Add(item); + } } return lookup; @@ -122,9 +125,7 @@ public IEnumerator> GetEnumerator() { do { - g = g._next; - - Debug.Assert(g is not null); + g = Assume.NotNull(g._next); yield return g; } while (g != _lastGrouping); @@ -179,10 +180,10 @@ void Resize() { var newSize = checked((_count * 2) + 1); var newGroupings = new Grouping[newSize]; - var g = _lastGrouping!; + var g = Assume.NotNull(_lastGrouping); do { - g = g._next!; + g = Assume.NotNull(g._next); var index = g._hashCode % newSize; g._hashNext = newGroupings[index]; newGroupings[index] = g; diff --git a/MoreLinq/Pad.cs b/MoreLinq/Pad.cs index e046b262b..9fc728c35 100644 --- a/MoreLinq/Pad.cs +++ b/MoreLinq/Pad.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; static partial class MoreEnumerable { diff --git a/MoreLinq/PartialSort.cs b/MoreLinq/PartialSort.cs index e8010510f..ecf8b1aec 100644 --- a/MoreLinq/PartialSort.cs +++ b/MoreLinq/PartialSort.cs @@ -242,7 +242,7 @@ static IEnumerable PartialSortByImpl( { if (keys != null) { - var key = keySelector!(item); + var key = Assume.NotNull(keySelector)(item); if (Insert(keys, key, keyComparer) is {} i) { if (top.Count == count) diff --git a/MoreLinq/Partition.cs b/MoreLinq/Partition.cs index 3b67ab6b3..047c48c0f 100644 --- a/MoreLinq/Partition.cs +++ b/MoreLinq/Partition.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; using System.Linq; static partial class MoreEnumerable