unnecessary-null-checks

Same as unnecessary_null_checks in Dart lints

Group
Style
Path
lint/style/unnecessary-null-checks
Default
Off, opt in
Origin
Dart lints
Rule
464 of 512

Flags a null assertion ! on a value that flows into a nullable context.

When x! is passed to a String? parameter, assigned to an int? variable, or returned from a function returning Object?, the context accepts null anyway: the ! only adds a runtime failure. The rule fires on ! whose value directly is:

  • the initializer of a variable or field declared with a nullable type;
  • the right side of a plain = assignment to a nullable target other than a local variable or parameter (the ! may promote those), unless the asserted operand is the assigned name itself (x = x!);
  • an argument for a nullable parameter, except Future.value(x!) and completer.complete(x!);
  • returned from a top-level or local function (including async functions and top-level getters), or yielded to a nullable stream/iterable element, including yield* (matching the analyzer's element-type check);
  • an element of a list or set literal, or a key or value of a map literal, whose element types are nullable;

and on a null-assert pattern p! of a pattern assignment whose matched value is nullable. In declarations, for loops, and case clauses the ! narrows the bound variables, so it is never flagged there.

dynamic and void accept null too. Unresolved contexts stay silent. Index writes and right-hand operator operands use their instantiated parameter types, not the enclosing expression's result context. Grouping parentheses preserve the nullable context of the inner assertion.

Invalid

example.dartdart
int? counter;

String? describe(int? value) => value?.toString();

void takeNullable(String? text) {}

int? lastLength;

class Holder {
  Holder(this.name);
  String? name;
  String? copy;
  String? _cache;
  late final String? _late;

  // Fields are never promotable on assignment, private or `late` alike.
  void fill(String? value) {
    _cache = value!;
    _late = value!;
    copy = value!;
  }
}

int? doubled(int? x) => x!;

List<String>? first(List<String>? items) {
  return items!;
}

void main(String? text, int? number, Holder holder, (int?, int) pair) {
  takeNullable(text!);
  int? copy = number!;
  holder.copy = text!;
  counter = number!;
  final list = <int?>[number!];
  final map = <String?, int?>{text!: 1};
  int? a;
  int b;
  (a!, b) = pair;
  print([copy, list, map, a, b, describe(1), doubled(2), first(null)]);
}

class UriContext {
  String fromUri(Object? uri) => '$uri';
}

final UriContext uriContext = UriContext();

// A top-level variable receiver resolves to its declared type.
String fromUri(Object? uri) => uriContext.fromUri(uri!);

// Analyzer 3.13.5: adv-AdvA/repro/r14_unnecessary_null_checks.dart.
void takesDynamic(dynamic value) {}
class NullableOperators {
  void operator []=(int index, int? value) {}
  NullableOperators operator +(int? value) => this;
}
dynamic returnsDynamic(int? value) => value!;
void nullableContexts(int? value, NullableOperators receiver, Map<String, int?> map) {
  takesDynamic(value!);
  receiver[0] = value!;
  receiver + value!;
  map['key'] = value!;
}
Future<int?> nullableAsync(int? value) async => value!;
Stream<int?> nullableStream(int? value) async* {
  yield value!;
}
// Official test_yieldSyncStar_iterableOfNullable and returnAsync_*_await.
Iterable<int?> nullableIterable(int? value) sync* {
  yield value!;
}
Future<int?> nullableAwait(int? value) async => await value!;
int? get nullableGetter => getterValue!;
int? getterValue;
Remove the unnecessary null check (`!`).
17 void fill(String? value) {
18 _cache = value!;
Remove the unnecessary null check (`!`).
18 _cache = value!;
19 _late = value!;
Remove the unnecessary null check (`!`).
19 _late = value!;
20 copy = value!;
Remove the unnecessary null check (`!`).
23
24int? doubled(int? x) => x!;
Remove the unnecessary null check (`!`).
26List<String>? first(List<String>? items) {
27 return items!;
Remove the unnecessary null check (`!`).
30void main(String? text, int? number, Holder holder, (int?, int) pair) {
31 takeNullable(text!);
Remove the unnecessary null check (`!`).
31 takeNullable(text!);
32 int? copy = number!;
Remove the unnecessary null check (`!`).
32 int? copy = number!;
33 holder.copy = text!;
Remove the unnecessary null check (`!`).
33 holder.copy = text!;
34 counter = number!;
Remove the unnecessary null check (`!`).
34 counter = number!;
35 final list = <int?>[number!];
Remove the unnecessary null check (`!`).
35 final list = <int?>[number!];
36 final map = <String?, int?>{text!: 1};
Remove the unnecessary null check (`!`).
38 int b;
39 (a!, b) = pair;
Remove the unnecessary null check (`!`).
49// A top-level variable receiver resolves to its declared type.
50String fromUri(Object? uri) => uriContext.fromUri(uri!);
Remove the unnecessary null check (`!`).
57}
58dynamic returnsDynamic(int? value) => value!;
Remove the unnecessary null check (`!`).
59void nullableContexts(int? value, NullableOperators receiver, Map<String, int?> map) {
60 takesDynamic(value!);
Remove the unnecessary null check (`!`).
60 takesDynamic(value!);
61 receiver[0] = value!;
Remove the unnecessary null check (`!`).
61 receiver[0] = value!;
62 receiver + value!;
Remove the unnecessary null check (`!`).
62 receiver + value!;
63 map['key'] = value!;
Remove the unnecessary null check (`!`).
64}
65Future<int?> nullableAsync(int? value) async => value!;
Remove the unnecessary null check (`!`).
66Stream<int?> nullableStream(int? value) async* {
67 yield value!;
Remove the unnecessary null check (`!`).
70Iterable<int?> nullableIterable(int? value) sync* {
71 yield value!;
Remove the unnecessary null check (`!`).
72}
73Future<int?> nullableAwait(int? value) async => await value!;
Remove the unnecessary null check (`!`).
73Future<int?> nullableAwait(int? value) async => await value!;
74int? get nullableGetter => getterValue!;

Valid

example.dartdart
import 'dart:async';
import 'dart:math';

void takeText(String text) {}

void takeNullable(String? text) {}

class Holder {
  String? name;

  int? size() => name!.length;

  void fill(String? value) {
    name = name!;
  }
}

int count(int? value) => value!;

String? Function() closureOf(String? s) => () => s!;

String firstOr(List<String?> xs, bool Function(String?) p, String? fallback) =>
    xs.firstWhere(p, orElse: () => fallback!)!;

void main(String? text, int? number, (int?, int) pair) async {
  takeText(text!);
  int copy = number!;
  String? local;
  local = text!;
  final future = Future<int?>.value(number!);
  final completer = Completer<int?>();
  completer.complete(number!);
  final list = [number!];
  final (int a, int b) = (pair.$1!, pair.$2);
  var (c!, d) = pair;
  takeNullable(text);
  print([copy, local, future, list, a, b, c, d, count(1)]);
}

// Analyzer 3.13.5: Future.value retains its null assertion even when its
// instantiated parameter is FutureOr<int?>.
void futureValue(int? value) => Future<int>.value(value!);

// intl number_format.dart:120,136: generic arguments are inferred from the
// non-nullable operands, not widened by the nullable assignment context.
int? significantDigits;
void setSignificantDigits(int value) {
  significantDigits = min(significantDigits!, value);
  significantDigits = max(significantDigits!, value);
}

// Flutter badge.dart/dropdown_menu.dart/icon.dart: the enclosing nullable
// context is not the right operand's expected parameter type for `??`.
void acceptsNullable(int? value) {}
int? fallbackValue;
void nullableFallback(int? value) {
  acceptsNullable(value ?? fallbackValue!);
  int? local = value ?? fallbackValue!;
  significantDigits = value ?? fallbackValue!;
  print(local);
}


class Registration<T extends Object> {
  T? instance;
  final ready = Completer<T>();
}

// Future.value and Completer.complete deliberately retain their null checks,
// including a generic receiver obtained from an instance field.
void completeRegistration<T extends Object>(Registration<T> registration) {
  Future<T>.value(registration.instance!);
  registration.ready.complete(registration.instance!);
}

Configure unnecessary-null-checks

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

falcon.jsonjson
{
  "linter": {
    "rules": {
      "style": {
        "unnecessary-null-checks": "error"
      }
    }
  }
}

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

example.dartdart
// falcon-ignore lint/style/unnecessary-null-checks: <reason>
  • unnecessary-nullable-for-final-variable-declarationsunnecessary_nullable_for_final_variable_declarationsrecommended

    Flags a final or const variable given a nullable type but initialized to a provably non-null value.

  • unnecessary-parenthesisunnecessary_parenthesisrecommended

    Flags parentheses that can be removed without changing the meaning or hurting readability.

  • unnecessary-raw-stringsunnecessary_raw_stringsrecommended

    Flags raw string literals that contain no backslash or $.

  • unnecessary-string-escapesunnecessary_string_escapesrecommended

    Flags backslashes that do not affect a string's value.

  • unnecessary-string-interpolationsunnecessary_string_interpolationsrecommended

    Flags a string literal whose entire content is a single interpolation of a provably non-nullable String.

  • unnecessary-thisunnecessary_thisrecommended

    Flags a redundant this. qualifier on member access.

  • unreachable-from-mainunreachable_from_main

    Report public declarations that no entry point of an executable library reaches.

  • use-colored-boxuse_colored_boxrecommendedflutter

    Prefers ColoredBox over a Container that only paints a color behind its child.