λ1018: Prefer Flatten over SelectMany with the identity function

SelectMany(x => x) collapses one level of nesting. Flatten() does the same and says so by name.

Cause

SelectMany is called with the identity function, x => x or Identity from Funcky.Functional, on a nested Option<Option<T>>, Result<Result<T>>, Either<L, Either<L, R>> or IEnumerable<IEnumerable<T>>. Only the SelectMany of the monads and of System.Linq.Enumerable counts: ParallelEnumerable and Queryable have their own, which Flatten would turn into a plain enumerable.

The overload with a result selector, query syntax (from x in xs from y in x select y) and SelectMany inside an expression tree are not reported.

Reason for rule

A selector that returns its argument is noise: the reader has to look at the lambda to find out that it does nothing. Flatten states the intent and needs no selector. λ1017 would otherwise turn the lambda into SelectMany(Identity), which is still a roundabout way of writing Flatten().

How to fix violations

Replace the SelectMany call with Flatten(). The code fix does this and adds the using Funcky.Monads; or using Funcky.Extensions; when the extension method is not yet in scope. Explicit type arguments select the element type and are kept: SelectMany<IEnumerable<string>, object>(x => x) becomes Flatten<object>(). The fix is only offered when the rewritten call reaches Funcky's Flatten; a Flatten member of the receiver's own type would win over the extension method, so there the call is reported without a fix.

Examples

Disallowed

static Option<int> Example(Option<Option<int>> option)
    => option.SelectMany(x => x);

static IEnumerable<int> Example(IEnumerable<IEnumerable<int>> values)
    => values.SelectMany(Identity);

Allowed

static Option<int> Example(Option<Option<int>> option)
    => option.Flatten();

static IEnumerable<int> Example(IEnumerable<IEnumerable<int>> values)
    => values.Flatten();

// The selector does something.
static Option<int> Example(Option<Option<int>> option)
    => option.SelectMany(inner => inner.Select(x => x + 1));