avoid-equals-and-hash-code-on-mutable-classes

Same as avoid_equals_and_hash_code_on_mutable_classes in Dart lints

Group
Style
Path
lint/style/avoid-equals-and-hash-code-on-mutable-classes
Default
Off, opt in
Origin
Dart lints
Rule
62 of 512

Flags == and hashCode overrides in classes not marked @immutable.

A value-based == and hashCode on a mutable object is a trap: once the object is stored in a Set or used as a Map key, mutating it changes its hash code and the collection silently loses track of it. Only classes that are (transitively) annotated with package:meta's @immutable should override them. The diagnostic covers the first declaration token after metadata.

class Point {                 // not @immutable
  int x;
  Point(this.x);
  @override
  bool operator ==(Object other) => other is Point && other.x == x; // flagged
  @override
  int get hashCode => x.hashCode; // flagged
}

The annotation may sit on the class itself or on any supertype (superclass, mixin, or interface). Unresolved supertypes leave immutability unknown.

Invalid

example.dartdart
import 'package:meta/meta.dart';

class Point {
  int x;
  Point(this.x);

  @override
  bool operator ==(Object other) => other is Point && other.x == x;

  @override
  int get hashCode => x.hashCode;
}

class Name {
  final String value;
  Name(this.value);

  @override
  bool operator ==(Object other) => other is Name && other.value == value;
}

class Id {
  final int id;
  Id(this.id);

  @override
  int get hashCode => id;
}

// A non-immutable superclass does not make the subclass immutable.
class Base {}

class Derived extends Base {
  @override
  bool operator ==(Object other) => identical(this, other);
}

mixin class Tagged {
  @override
  int get hashCode => 7;
}

// `@immutable` on an unrelated declaration does not apply.
@immutable
class Frozen {
  const Frozen();
}

class Unrelated {
  @override
  bool operator ==(Object other) => other is Unrelated;
}

// SDK supertypes never carry `@immutable`.
class Ordered implements Comparable<Ordered> {
  int value = 0;
  @override
  int compareTo(Ordered other) => 0;
  @override
  bool operator ==(Object other) => other is Ordered && other.value == value;
}
The method `==` should not be overridden in classes not annotated with `@immutable`.
7 @override
8 bool operator ==(Object other) => other is Point && other.x == x;
The method `hashCode` should not be overridden in classes not annotated with `@immutable`.
10 @override
11 int get hashCode => x.hashCode;
The method `==` should not be overridden in classes not annotated with `@immutable`.
18 @override
19 bool operator ==(Object other) => other is Name && other.value == value;
The method `hashCode` should not be overridden in classes not annotated with `@immutable`.
26 @override
27 int get hashCode => id;
The method `==` should not be overridden in classes not annotated with `@immutable`.
34 @override
35 bool operator ==(Object other) => identical(this, other);
The method `hashCode` should not be overridden in classes not annotated with `@immutable`.
39 @override
40 int get hashCode => 7;
The method `==` should not be overridden in classes not annotated with `@immutable`.
50 @override
51 bool operator ==(Object other) => other is Unrelated;
The method `==` should not be overridden in classes not annotated with `@immutable`.
59 @override
60 bool operator ==(Object other) => other is Ordered && other.value == value;

Valid

example.dartdart
import 'package:meta/meta.dart';
import 'package:meta/meta.dart' as m;

@immutable
class Point {
  final int x;
  const Point(this.x);

  @override
  bool operator ==(Object other) => other is Point && other.x == x;

  @override
  int get hashCode => x.hashCode;
}

// Immutability is inherited from an annotated supertype.
@immutable
abstract class Value {
  const Value();
}

class Meters extends Value {
  final double meters;
  const Meters(this.meters);

  @override
  bool operator ==(Object other) => other is Meters && other.meters == meters;

  @override
  int get hashCode => meters.hashCode;
}

class Kilometers implements Value {
  final double km;
  const Kilometers(this.km);

  @override
  bool operator ==(Object other) => other is Kilometers && other.km == km;

  @override
  int get hashCode => km.hashCode;
}

// Mutable classes that keep identity equality are fine.
class Counter {
  int count = 0;
  void increment() => count++;
}

// Other operators are not reported.
class Vector {
  int x = 0;
  Vector operator +(Vector other) => Vector()..x = x + other.x;
}

// Extensions cannot override `==`; enums cannot either.
enum Mode { on, off }

@m.Immutable('x')
class ImmutableReason {
  @override
  bool operator ==(Object other) => true;
}

@Immutable()
class ImmutableConstructor {
  @override
  int get hashCode => 0;
}

Configure avoid-equals-and-hash-code-on-mutable-classes

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

falcon.jsonjson
{
  "linter": {
    "rules": {
      "style": {
        "avoid-equals-and-hash-code-on-mutable-classes": "error"
      }
    }
  }
}

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

example.dartdart
// falcon-ignore lint/style/avoid-equals-and-hash-code-on-mutable-classes: <reason>