avoid-unsafe-reduce

Path
lint/correctness/avoid-unsafe-reduce
Default
Off, opt in
Origin
Falcon
Rule
181 of 512

Warn when SDK Iterable.reduce can receive an empty collection.

Constant nonempty collections and fresh literals with an unconditional element are safe, including reduce as the first cascade operation. Immediate isEmpty/isNotEmpty branches require a local or final-field receiver and no intervening expression that can mutate it. Custom reduce members and unresolved receivers are excluded.

Invalid

example.dartdart
int sum(int a, int b) => a + b;

void unsafe(List<int> list, Set<int> set, Iterable<int> iterable, List<int>? nullable) {
  list.reduce(sum);
  set.reduce((a, b) => a + b);
  iterable.reduce(sum);
  nullable?.reduce(sum);
  <int>[].reduce(sum);
  list..reduce(sum);
  if (list.isNotEmpty) {
    list.clear();
    list.reduce((a, b) => a + b);
  }
}

int mutatingCallback(List<int> list) {
  int Function(int, int) callback() {
    list.clear();
    return sum;
  }
  return list.isNotEmpty ? list.reduce(callback()) : 0;
}

var reads = 0;
List<int> get values => reads++ == 0 ? [1] : [];
int unstableGetter() =>
    values.isNotEmpty ? values.reduce((a, b) => a + b) : 0;

void mutatedCascades() {
  [1, 2]..clear()..reduce(sum);
  [1, 2]..length = 0..reduce(sum);
}

class UnstableReceiver {
  List<int> values = [];
  List<int> get items => [];
  int field() =>
      values.isNotEmpty ? values.reduce(sum) : 0;
  int getter() =>
      items.isNotEmpty ? items.reduce(sum) : 0;
}

class StaticReceiver {
  static List<int> values = [];
  int sum() =>
      values.isNotEmpty ? values.reduce((a, b) => a + b) : 0;
}

abstract class AbstractReceiver {
  abstract final List<int> values;
  int sum() =>
      values.isNotEmpty ? values.reduce((a, b) => a + b) : 0;
}

extension EmptyingRead on List<int> {
  int get empty {
    clear();
    return 0;
  }
}

void getterCascade() {
  [1]..empty..reduce(sum);
  [1, 2]..length..reduce(sum);
}
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
3void unsafe(List<int> list, Set<int> set, Iterable<int> iterable, List<int>? nullable) {
4 list.reduce(sum);
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
4 list.reduce(sum);
5 set.reduce((a, b) => a + b);
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
5 set.reduce((a, b) => a + b);
6 iterable.reduce(sum);
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
6 iterable.reduce(sum);
7 nullable?.reduce(sum);
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
7 nullable?.reduce(sum);
8 <int>[].reduce(sum);
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
8 <int>[].reduce(sum);
9 list..reduce(sum);
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
11 list.clear();
12 list.reduce((a, b) => a + b);
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
20 }
21 return list.isNotEmpty ? list.reduce(callback()) : 0;
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
26int unstableGetter() =>
27 values.isNotEmpty ? values.reduce((a, b) => a + b) : 0;
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
29void mutatedCascades() {
30 [1, 2]..clear()..reduce(sum);
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
30 [1, 2]..clear()..reduce(sum);
31 [1, 2]..length = 0..reduce(sum);
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
37 int field() =>
38 values.isNotEmpty ? values.reduce(sum) : 0;
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
39 int getter() =>
40 items.isNotEmpty ? items.reduce(sum) : 0;
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
45 int sum() =>
46 values.isNotEmpty ? values.reduce((a, b) => a + b) : 0;
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
51 int sum() =>
52 values.isNotEmpty ? values.reduce((a, b) => a + b) : 0;
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
62void getterCascade() {
63 [1]..empty..reduce(sum);
Reducing a possibly empty iterable can throw; use fold or check for emptiness.
63 [1]..empty..reduce(sum);
64 [1, 2]..length..reduce(sum);

Valid

example.dartdart
class Custom {
  int reduce(int Function(int, int) combine) => 0;
}

int safe(List<int> list, Custom custom, dynamic unknown) {
  custom.reduce((a, b) => a + b);
  unknown.reduce((a, b) => a + b);
  [1, 2].reduce((a, b) => a + b);
  <int>{1}.reduce((a, b) => a + b);
  [1, 2]..reduce((a, b) => a + b);
  <int>{1}..reduce((a, b) => a + b);
  const values = [1, 2];
  values.reduce((a, b) => a + b);
  list.fold<int>(0, (a, b) => a + b);
  if (list.isNotEmpty) {
    return list.reduce((a, b) => a + b);
  }
  return 0;
}

int guarded(List<int> list) => list.isEmpty ? 0 : list.reduce((a, b) => a + b);
int negated(List<int> list) {
  if (!list.isEmpty) return list.reduce((a, b) => a + b);
  return 0;
}

extension CustomReduce on String {
  String reduce(String Function(String, String) combine) => this;
}
String customExtension(String value) => value.reduce((a, b) => a + b);

class StableReceiver {
  final List<int> values;
  StableReceiver(this.values);
  int sum() => values.isNotEmpty ? values.reduce((a, b) => a + b) : 0;
}

void nonemptyCascadeSections() {
  const values = [1, 2];
  values..reduce((a, b) => a + b);
}

Configure avoid-unsafe-reduce

Set its severity in falcon.json. See the configuration guide.

falcon.jsonjson
{
  "linter": {
    "rules": {
      "correctness": {
        "avoid-unsafe-reduce": "error"
      }
    }
  }
}

Or silence one occurrence with a suppression comment; the reason is required.

example.dartdart
// falcon-ignore lint/correctness/avoid-unsafe-reduce: <reason>
  • avoid-unused-after-null-check

    Reports a null-checked local ignored by a branch that passes another local of the same resolved type to a call.

  • avoid-unused-parametersavoid-unused-parametersrecommended

    Disallow declared parameters that are never used, excluding primary constructors.

  • avoid-watch-outside-buildrecommendedprovider

    Flags listening Provider accesses proven to run outside Flutter builds.

  • avoid-web-libraries-in-flutteravoid_web_libraries_in_flutterrecommendedflutter

    Disallow importing web-only dart: libraries from Flutter code.

  • await-only-futuresawait_only_futuresrecommended

    Await only values assignable to futures; FutureOr, dynamic, bottom types and extension types are accepted like the analyzer.

  • banned-importscross-file

    Report imports forbidden by package-relative architectural boundaries.

  • cancel-subscriptionscancel_subscriptionsrecommended

    Cancel instances of dart:async StreamSubscription.

  • check-is-not-closed-after-async-gaprecommendedbloc

    Flags Bloc add and emit calls after an await without a fresh isClosed check.