λ0005: A null literal is passed to Option, Result or Either
Option<T>, Result<T> and Either<L, R> never hold null. A null literal that becomes their value throws an
ArgumentNullException every time the code runs.
Cause
A null literal is converted to Option<T>, Result<T> or Either<L, R> through the implicit conversion from the
inner type, or it is passed to one of the factory methods Option.Some, Option.Return, Result.Ok, Result.Return,
Result<T>.Error, Either<L, R>.Left, Either<L, R>.Right or Either<L>.Return.
The literal may sit in a branch of a conditional (?:), switch or null-coalescing (??) expression whose
result is converted.
Reason for rule
The implicit conversion Option<string> option = null; does not produce an empty option: it calls Option.Some(null),
which throws an ArgumentNullException. The same holds for Result<T> and Either<L, R>.
The comparison option == null is the sneakiest form. It compiles, because null converts to Option<string>
through the same implicit conversion, and it throws at run time instead of answering false.
With nullable reference types enabled the compiler reports CS8625 for some of these cases. With nullable reference types disabled it is silent, so this rule is an error in both configurations.
Only the literal is reported. Whether a variable or a method call yields null is a runtime question, and
Option.FromNullable exists for exactly that case.
How to fix violations
For an option use Option<T>.None, the code fix does this. A call Option.Some(null) is replaced as a whole.
For a result or an either, construct the error or left value explicitly, e.g. Result<string>.Error(exception) or
Either<string, int>.Left("message"). A value that might be null goes through Option.FromNullable(value).
Examples
Disallowed
Option<string> option = null; // Throws ArgumentNullException.
bool IsEmpty(Option<string> option) => option == null; // Throws instead of comparing.
Option<string> Name(bool condition) => condition ? "hello" : null;
Option<string> Empty() => Option.Some<string>(null);
Result<string> Missing() => Result.Ok<string>(null);
Allowed
Option<string> option = Option<string>.None;
bool IsEmpty(Option<string> option) => option == Option<string>.None;
Option<string> Name(bool condition) => condition ? "hello" : Option<string>.None;
// A default option is a valid None.
Option<string> Empty() => default;
// A lifted conversion to a nullable option propagates the null instead of calling the operator.
Option<int>? Missing() => (int?)null;
// Whether a variable is null is decided at run time.
Option<string> Lookup(string value) => Option.FromNullable(value);