Skip to content

avoid_unnecessary_option

v1.0.0WarningConfigurablefpdart

This rule flags a local variable holding an Option that is never composed — wrapped, then immediately unwrapped with toNullable or getOrElse.

Option earns its keep through its combinators: chaining with flatMap, promoting absence to a failure with toEither, providing a fallback with alt. A local that is wrapped and then unwrapped on the next line gets none of that, and pays for it — Dart’s nullable types have language support (?., ??, narrowing after a null check) that Option cannot match.

fpdart’s own author makes the same point: Option<T> and T? are not mutually exclusive. Nullable wins on language support, Option wins on declarative chaining, and the right move is to convert at the border rather than fight the language.

See also: Option type and null safety in Dart

final option = Option.fromNullable(name);
final value = option.toNullable() ?? 'unknown';

Either drop the wrapper:

final value = name ?? 'unknown';

Or earn it, by actually composing:

final value = Option.fromNullable(name)
.map(deriveDisplayName)
.alt(() => some(defaultName))
.getOrElse(() => 'unknown');

Only local variables are reported. A field or a parameter is part of a type’s shape, and reporting it would be a claim about the API rather than about one expression.

A local inside a public member is skipped by default, since its Option may be feeding a contract. Set ignore_public_api: false to report those too.

An Option passed to a function, returned, or matched with a pattern counts as composed — the wrapper is doing work in all three.

No quick fix is offered: rewriting to a nullable means adjusting every downstream use, which is a change to read rather than apply.

analysis_options.yaml
many_lints:
rules:
avoid_unnecessary_option:
ignore_public_api: false
Option Type Default Description
ignore_public_api bool true Skip locals inside public members, whose Option may be feeding a contract

This rule is in the pedantic preset — a codebase that has standardised on Option everywhere is making a coherent choice, and this rule disagrees with it. It can also be enabled explicitly:

many_lints.yaml
rules:
avoid_unnecessary_option: true

To keep the rule on but skip certain paths, use per-rule exclude.