Skip to content

prefer_switch_expression

v0.3.0WarningFixConfigurableControl Flow

This rule flags a switch statement whose every case is a single statement and where all of them do the same kind of thing: return a value, or assign to the same variable.

That switch is producing a value while pretending to be control flow. A switch expression says so, drops the case/return scaffolding, and — because it is an expression — the compiler checks exhaustiveness at the point the value is needed. The quick fix does the conversion.

See also: Switch expressions

Every case returns:

enum DeliveryStage { packed, shipped, delivered }
enum DeliveryIcon { box, truck, home }
DeliveryIcon iconFor(DeliveryStage stage) {
switch (stage) {
case DeliveryStage.packed:
return DeliveryIcon.box;
case DeliveryStage.shipped:
return DeliveryIcon.truck;
case DeliveryStage.delivered:
return DeliveryIcon.home;
}
}

Every case assigns the same variable, which then has to be declared uninitialised above:

enum DeliveryIcon { box, truck, home }
String describe(DeliveryIcon icon) {
String description;
switch (icon) {
case DeliveryIcon.box:
description = 'Waiting in the warehouse';
case DeliveryIcon.truck:
description = 'On the road';
case DeliveryIcon.home:
description = 'Dropped at the door';
}
return description;
}
enum DeliveryStage { packed, shipped, delivered }
enum DeliveryIcon { box, truck, home }
DeliveryIcon iconFor(DeliveryStage stage) => switch (stage) {
DeliveryStage.packed => DeliveryIcon.box,
DeliveryStage.shipped => DeliveryIcon.truck,
DeliveryStage.delivered => DeliveryIcon.home,
};

The assigning form collapses into a final, so the variable is never in an unassigned state:

enum DeliveryIcon { box, truck, home }
String describe(DeliveryIcon icon) {
final description = switch (icon) {
DeliveryIcon.box => 'Waiting in the warehouse',
DeliveryIcon.truck => 'On the road',
DeliveryIcon.home => 'Dropped at the door',
};
return description;
}

A default becomes the wildcard _:

String name(int value) => switch (value) {
1 => 'one',
2 => 'two',
_ => 'unknown',
};

Anything that is not a clean value-producing switch is left alone, because the rewrite would not be exact:

  • A case with more than one statement.
  • A case that falls through (empty body) — unless allow_fallthrough_cases is on, see below.
  • Mixed cases: some returning, some assigning.
  • Cases assigning to different variables.

This rule is in the opinionated preset, so it is on with preset: opinionated, or by name:

many_lints.yaml
rules:
prefer_switch_expression: true

To turn it off again:

many_lints.yaml
rules:
prefer_switch_expression: false

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

Off by default. Turn it on to also report a switch where several cases share one body — the quick fix merges their patterns with ||:

analysis_options.yaml
many_lints:
rules:
prefer_switch_expression:
allow_fallthrough_cases: true
enum DeliveryStage { packed, shipped, delivered }
// Don't
bool isMoving(DeliveryStage stage) {
switch (stage) {
case DeliveryStage.packed:
case DeliveryStage.shipped:
return true;
case DeliveryStage.delivered:
return false;
}
}
// Do
bool isMovingExpression(DeliveryStage stage) => switch (stage) {
DeliveryStage.packed || DeliveryStage.shipped => true,
DeliveryStage.delivered => false,
};

A trailing fallthrough — an empty last case with nothing after it to share a body with — has no pattern to merge into and stays unreported even with the option on.

Option Type Default Description
allow_fallthrough_cases bool false Also report switches where cases share a body. The quick fix merges them into a single case a || b pattern; a trailing fallthrough has nothing to merge into and stays unreported