λ0004: The default literal is wrapped by an implicit conversion
A bare default in one branch of an expression that is implicitly converted to Option<T>, Result<T> or Either<L, R>
is not the empty monad, it is default(T) wrapped in a Some, an Ok or a Right.
Cause
A default literal is a branch of a conditional (?:), switch or null-coalescing (??) expression, and the whole
expression is converted to Option<T>, Result<T> or Either<L, R> through the implicit conversion from the inner type.
Reason for rule
The default literal has no type of its own; it takes the type of the sibling branch. In
condition ? 42 : default both branches are int, so the expression is an int and the implicit conversion
to Option<int> turns the default branch into Some(0). The code reads as "42 or nothing" but never produces None.
The same happens with Result<T> (an Ok(default) instead of an error) and Either<L, R> (a Right(default) instead
of a left). For reference types the compiler reports a nullable warning (CS8604), for value types and tuples it is silent.
A default that is converted directly, without a sibling branch, is fine: Option<int> option = default; is None.
A typed default(T) is not reported either, it says what it produces, and neither is a cast such as (int)default outside
a branch.
How to fix violations
For an option replace the literal with Option<T>.None, the code fix does this.
For a result or an either, construct the error or left value explicitly, e.g. Result<int>.Error(exception) or
Either<string, int>.Left("message").
Examples
Disallowed
Option<(string, int)> Lookup(bool condition)
=> condition ? ("hello world", 42) : default; // Some((null, 0)), not None.
Option<int> Parse(int? value) => value ?? default; // Some(0) for null.
Option<string> Name(int id)
=> id switch
{
1 => "one",
_ => default, // Some(null).
};
Result<int> Compute(bool condition) => condition ? 42 : default; // Ok(0), not an error.
Allowed
Option<(string, int)> Lookup(bool condition)
=> condition ? ("hello world", 42) : Option<(string, int)>.None;
Option<int> Parse(int? value) => Option.FromNullable(value);
Option<string> Name(int id)
=> id switch
{
1 => "one",
_ => Option<string>.None,
};
Result<int> Compute(bool condition)
=> condition ? 42 : Result<int>.Error(new InvalidOperationException());
// Converted directly, there is no sibling branch: this is None.
Option<int> Empty() => default;