Repository navigation
Add built-in rule λ0005: a null literal passed to Option, Result or Either - #972
Merged
Merged
Conversation
…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>
There was a problem hiding this comment.
🟡 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
λ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>
JudoJosef
approved these changes
Oct 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
nullliteral that is converted toOption<T>,Result<T>orEither<L, R>through the implicit conversion, or passed toOption.Some/Return,Result.Ok/Return,Result<T>.Error,Either<L, R>.Left/RightorEither<L>.Return. All of these throw anArgumentNullException, so the code throws every time it runs. The literal may sit in a branch of a?:,switchor??expression (same walker as λ0004 in Add built-in analyzer λ0004: default literal wrapped by an implicit conversion #966).option == null:nullconverts toOption<string>through the implicit operator, so the comparison compiles and throws instead of answeringfalse(verified at run time against the current library).Option<T>.None, or the wholeOption.Some(null)call. No fix forResult/Either, the intended error or left value is not knowable.null(that is whatOption.FromNullableis for),default,Option.FromNullable(null).Funcky.Testthat passnullon purpose to assert the exception suppress the rule with a pragma.λ0005.md, rule table, SUMMARY, changelog.Shares the
CompilationExtensionsadditions and theDiagnosticName.cscompile include with #966 (byte-identical, merges cleanly); theDiagnosticNameconstant and the Unshipped/changelog/table lines are adjacent-line conflicts.Test plan
NullToMonadTest(conversion in assignment/return/argument/==/?:/switch/??, factory methods, Result/Either without fix, variables and unrelated conversions not reported); 86 analyzer tests passdotnet build Funcky.slnx -warnaserror --no-incrementalgreen (the rule found exactly the six intentional-null tests)Funcky.Test.Monadstests pass on net10.0🤖 Generated with Claude Code