Skip to content

Add analyzer λ1018: prefer Flatten over SelectMany with the identity function - #974

Merged
FreeApophis merged 5 commits into
mainfrom
analyzer-prefer-flatten
Oct 20, 2026
Merged

FreeApophis merged 5 commits into
mainfrom
analyzer-prefer-flatten

Conversation

@FreeApophis

Copy link
Copy Markdown
Member

Summary

  • New opt-in rule λ1018 (Warning): SelectMany(x => x) and SelectMany(Identity) on a nested Option<Option<T>>, Result<Result<T>>, Either<L, Either<L, R>> or IEnumerable<IEnumerable<T>> is Flatten(). After λ1017 (Add analyzer λ1017: prefer the named function over the lambda #970) the lambda form becomes SelectMany(Identity), which makes the gap more visible, not less; this rule closes it.
  • Also reported: a lambda that returns its parameter through an implicit reference conversion (IEnumerable<int[]>.SelectMany(x => x)), since Flatten accepts the receiver through the same covariance. Any type implementing IEnumerable<T> counts as receiver (List<int[]>).
  • Not reported: a selector that does something, the result-selector overload and query syntax, the static call form Enumerable.SelectMany(xs, x => x), expression trees, and compilations without the matching Flatten extension.
  • Code fix replaces the call with Flatten() and adds using Funcky.Monads; / using Funcky.Extensions; when the extension does not bind yet (same UsingDirectives helper as Add analyzer λ1012: prefer the …OrNone method over …OrDefault with a null check #973, here it also takes the file's own line ending for files without usings; I'll port that line to Add analyzer λ1012: prefer the …OrNone method over …OrDefault with a null check #973).
  • Dogfooding: Sequence.Concat, CycleMaterialized, RepeatMaterialized and one test now call Flatten(); the four Flatten implementations themselves suppress the rule with a pragma.
  • Docs: λ1018.md, rule table, SUMMARY, changelog, AnalyzerReleases.Unshipped.

Test plan

  • 5 new tests in PreferFlattenTest: Option (lambda, block body, method group, chained), Result/Either, IEnumerable with using added and List<int[]> receiver, using added for a fully qualified monad type, the not-reported forms
  • 81 analyzer tests pass, Roslyn 4.0 variant builds
  • dotnet build Funcky.slnx -warnaserror --no-incremental green; Sequence, Flatten and Split tests pass on net10.0

🤖 Generated with Claude Code

…function

SelectMany(x => x) and SelectMany(Identity) on a nested Option, Result,
Either or IEnumerable collapse one level of nesting; Flatten() does the
same and says so by name. The lambda may return its parameter through an
implicit reference conversion (IEnumerable<int[]>), which Flatten accepts
through the same covariance. The result-selector overload, query syntax,
the static call form and expression trees are not reported.

The code fix replaces the call and adds `using Funcky.Monads;` or
`using Funcky.Extensions;` when the extension does not bind yet. The
using helper takes the file's own line ending for a file without usings.

Dogfooding: Sequence.Concat, CycleMaterialized, RepeatMaterialized and a
test use Flatten; the four Flatten implementations suppress the rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unsafe replacements and syntax-handling failures can change behavior, break compilation, or prevent fixes from applying.

6 open issues
What changed in this PR

Adds λ1018 to recommend Flatten() for identity-based SelectMany calls, with a code fix, tests, documentation, and adoption in existing code.

Changes:

  • Detects supported identity selectors and offers a Flatten() replacement.
  • Adds namespace-import handling and analyzer tests.
  • Documents the rule and updates existing flattening calls.
File Description
Funcky/​Sequence/​Sequence.RepeatMaterialized.cs Uses Flatten().
Funcky/​Sequence/​Sequence.CycleMaterialized.cs Uses Flatten().
Funcky/​Sequence/​Sequence.Concat.cs Uses Flatten().
Funcky/​Monads/​Result/​ResultExtensions.cs Suppresses λ1018 inside Flatten.
Funcky/​Monads/​Option/​OptionExtensions.cs Suppresses λ1018 inside Flatten.
Funcky/​Monads/​Either/​EitherExtensions.cs Suppresses λ1018 inside Flatten.
Funcky/​Extensions/​EnumerableExtensions/​Flatten.cs Suppresses λ1018 inside Flatten.
Funcky.Test/​Extensions/​EnumerableExtensions/​SplitTest.cs Uses Flatten() in a property test.
Funcky.Analyzers/​Funcky.Analyzers/​Flatten/​PreferFlattenAnalyzer.cs Implements λ1018 detection.
Funcky.Analyzers/​Funcky.Analyzers/​AnalyzerReleases.Unshipped.md Registers the new rule.
Funcky.Analyzers/​Funcky.Analyzers.Test/​Flatten/​PreferFlattenTest.cs Tests diagnostics and fixes.
Funcky.Analyzers/​Funcky.Analyzers.CodeFixes/​Flatten/​PreferFlattenCodeFix.cs Implements the replacement fix.
Funcky.Analyzers/​Funcky.Analyzers.CodeFixes/​CodeAnalysisExtensions/​UsingDirectives.cs Adds namespace imports.
Documentation/​src/​SUMMARY.md Links the rule documentation.
Documentation/​src/​analyzer-rules/​λ1018.md Explains the rule and examples.
Documentation/​src/​analyzer-rules/​analyzer-rules.md Adds λ1018 to the rule table.
changelog.md Announces the analyzer and fix.

Review effort: Balanced

Comment thread Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Flatten/PreferFlattenCodeFix.cs Outdated
Comment thread Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Flatten/PreferFlattenCodeFix.cs Outdated
Comment thread Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Flatten/PreferFlattenCodeFix.cs Outdated
Comment thread Funcky.Analyzers/Funcky.Analyzers/Flatten/PreferFlattenAnalyzer.cs
FreeApophis and others added 4 commits October 20, 2026 15:38
…y, and behind leading #define directives

The directive-safe insertion of the λ1017 fix moves into the shared UsingDirectives helper, which copies only whitespace
and line endings from its neighbour. In a file without usings a leading `#define` or `#undef` moves in front of the new
using, since those directives must precede every token.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…and match only the owning SelectMany

- The analyzer only reports the SelectMany of the monads and of System.Linq.Enumerable: ParallelEnumerable and Queryable
  have their own, which Flatten would turn into a plain enumerable. The rule uses the Funcky.Style category.
- The fix keeps the last explicit type argument as `Flatten<T>()`, tracks the replacement with an annotation instead of
  the old span, and is only offered when the rewritten call (with the using added if necessary) binds to Funcky's
  Flatten; a Flatten member of the receiver's own type leaves the call reported without a fix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@FreeApophis
FreeApophis enabled auto-merge October 20, 2026 14:15
@FreeApophis
FreeApophis merged commit 0fed143 into main Oct 20, 2026
10 checks passed
@FreeApophis
FreeApophis deleted the analyzer-prefer-flatten branch October 20, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants