Skip to content

Add built-in rule λ0005: a null literal passed to Option, Result or Either - #972

Merged
FreeApophis merged 4 commits into
mainfrom
analyzer-null-converted-to-monad
Oct 20, 2026
Merged

FreeApophis merged 4 commits into
mainfrom
analyzer-null-converted-to-monad

Conversation

@FreeApophis

Copy link
Copy Markdown
Member

Summary

  • New built-in rule λ0005 (Error): a null literal that is converted to Option<T>, Result<T> or Either<L, R> through the implicit conversion, or passed to Option.Some/Return, Result.Ok/Return, Result<T>.Error, Either<L, R>.Left/Right or Either<L>.Return. All of these throw an ArgumentNullException, so the code throws every time it runs. The literal may sit in a branch of a ?:, switch or ?? expression (same walker as λ0004 in Add built-in analyzer λ0004: default literal wrapped by an implicit conversion #966).
  • The sneakiest form is option == null: null converts to Option<string> through the implicit operator, so the comparison compiles and throws instead of answering false (verified at run time against the current library).
  • With nullable reference types disabled the compiler is silent about all of this; with them enabled it reports CS8625 for some forms. The rule is an error in both configurations.
  • Code fix: replaces the literal with Option<T>.None, or the whole Option.Some(null) call. No fix for Result/Either, the intended error or left value is not knowable.
  • Not reported: variables and calls that may be null (that is what Option.FromNullable is for), default, Option.FromNullable(null).
  • Six tests in Funcky.Test that pass null on purpose to assert the exception suppress the rule with a pragma.
  • Docs: λ0005.md, rule table, SUMMARY, changelog.

Shares the CompilationExtensions additions and the DiagnosticName.cs compile include with #966 (byte-identical, merges cleanly); the DiagnosticName constant and the Unshipped/changelog/table lines are adjacent-line conflicts.

Test plan

  • 9 new tests in NullToMonadTest (conversion in assignment/return/argument/==/?:/switch/??, factory methods, Result/Either without fix, variables and unrelated conversions not reported); 86 analyzer tests pass
  • dotnet build Funcky.slnx -warnaserror --no-incremental green (the rule found exactly the six intentional-null tests)
  • Funcky.Test.Monads tests pass on net10.0

🤖 Generated with Claude Code

…ither

The implicit conversions and the factory methods (Some, Return, Ok, Error,
Left, Right) throw an ArgumentNullException for null, so `Option<string> o
= null;`, `Option.Some<string>(null)` and even `option == null` throw every
time they run. With nullable reference types disabled the compiler is
silent about it. The literal may sit in a branch of a conditional, switch
or null-coalescing expression; variables and calls are not inspected.

The code fix replaces the literal with Option<T>.None, or the whole
Option.Some(null)/Option.Return(null) call. Result and Either get no fix.

The six Funcky.Test methods that pass null on purpose to assert the
exception 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

Lifted conversions can trigger false errors, and code fixes can produce invalid code or incorrect Option values.

3 open issues
What changed in this PR

Adds built-in error rule λ0005 to detect null literals passed to null-rejecting monad conversions and factories.

Changes:

  • Adds the analyzer and an Option-to-None code fix.
  • Adds regression tests and suppressions for intentional-null tests.
  • Documents and registers the new rule.
File Description
Funcky.Test/​Monads/​ResultTest.cs Suppresses intentional-null diagnostics.
Funcky.Test/​Monads/​OptionTest.cs Suppresses intentional-null diagnostics.
Funcky.Test/​Monads/​OptionImplicitConversionTest.cs Suppresses intentional-null diagnostics.
Funcky.Test/​Monads/​EitherTest.cs Suppresses intentional-null diagnostics.
Funcky.Analyzers/​Funcky.BuiltinAnalyzers/​NullToMonadAnalyzer.cs Implements λ0005 detection.
Funcky.Analyzers/​Funcky.BuiltinAnalyzers/​DiagnosticName.cs Defines the shared diagnostic ID.
Funcky.Analyzers/​Funcky.BuiltinAnalyzers/​CompilationExtensions.cs Adds monad type lookups.
Funcky.Analyzers/​Funcky.BuiltinAnalyzers/​AnalyzerReleases.Unshipped.md Registers the new rule.
Funcky.Analyzers/​Funcky.BuiltinAnalyzers.CodeFixes/​NullToOptionNoneCodeFix.cs Implements Option replacements.
Funcky.Analyzers/​Funcky.BuiltinAnalyzers.CodeFixes/​Funcky.BuiltinAnalyzers.CodeFixes.csproj Includes shared diagnostic definitions.
Funcky.Analyzers/​Funcky.Analyzers.Test/​NullToMonadTest.cs Tests detection and fixes.
Documentation/​src/​SUMMARY.md Links the rule documentation.
Documentation/​src/​analyzer-rules/​λ0005.md Explains the rule and remediation.
Documentation/​src/​analyzer-rules/​analyzer-rules.md Lists λ0005.
changelog.md Announces the analyzer and fix.

Review effort: Balanced

Comment thread Funcky.Analyzers/Funcky.BuiltinAnalyzers.CodeFixes/NullToOptionNoneCodeFix.cs Outdated
Comment thread Funcky.Analyzers/Funcky.BuiltinAnalyzers.CodeFixes/NullToOptionNoneCodeFix.cs Outdated
Comment thread Funcky.Analyzers/Funcky.BuiltinAnalyzers/NullToMonadAnalyzer.cs Outdated
FreeApophis and others added 3 commits October 20, 2026 12:51
λ0005 joins the built-in rules from #964 and #966 in the release file,
the rules table, the summary and the changelog. DiagnosticName carries
the λ0004 and λ0005 IDs next to the category, CompilationExtensions
keeps the Result and Either<L> lookups from this branch.

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

A lifted conversion to a nullable monad propagates the null without
calling the operator, so `Option<int>? option = (int?)null` is no longer
reported. The code fix looks for the first user-defined conversion above
the literal and offers None only when that conversion targets Option, so
`result == null` returned as Option<bool> gets no fix; a cast on the way
declines the fix too, since it would reject or box the None. λ0005
takes the Funcky.Correctness category.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@FreeApophis
FreeApophis merged commit bcc4d16 into main Oct 20, 2026
10 checks passed
@FreeApophis
FreeApophis deleted the analyzer-null-converted-to-monad branch October 20, 2026 13:05
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