prefer_overriding_parent_equality
Flags a concrete subclass that does not override both == and hashCode when an ancestor overrides them. The inherited equality ignores whatever the child added, so two children that differ compare equal.
PremiumAccount inherits Account’s equality, which compares id only. Two premium accounts on different tiers are now indistinguishable — to ==, to Set, and to any state comparison that rebuilds on change:
class Account { const Account(this.id);
final String id;
@override bool operator ==(Object other) => other is Account && id == other.id;
@override int get hashCode => id.hashCode;}
class PremiumAccount extends Account { const PremiumAccount(super.id, this.tier);
final String tier;}class PremiumAccount extends Account { const PremiumAccount(super.id, this.tier);
final String tier;
@override bool operator ==(Object other) => other is PremiumAccount && id == other.id && tier == other.tier;
@override int get hashCode => Object.hash(id, tier);}Overriding only one of the pair
Section titled “Overriding only one of the pair”Overriding == without hashCode is the same bug wearing a disguise: the objects compare equal but hash differently, so a Set or Map keeps both. Each half is reported on its own:
// Don't — == is overridden, hashCode is notclass PremiumAccount extends Account { const PremiumAccount(super.id, this.tier);
final String tier;
@override bool operator ==(Object other) => other is PremiumAccount && id == other.id && tier == other.tier;}A parent with no equality of its own is not reported
Section titled “A parent with no equality of its own is not reported”The rule fires only when an ancestor overrides both == and hashCode. Plain inheritance is left alone:
// No warning — Point does not override equalityclass Point { const Point(this.x);
final int x;}
class Point3D extends Point { const Point3D(super.x, this.z);
final int z;}Abstract classes are skipped too — the concrete subclass is where the override belongs.
Widgets and test doubles are never reported
Section titled “Widgets and test doubles are never reported”Three types are exempt by default, each because its identity is deliberately not its fields:
| Type | Why | Where it is declared |
|---|---|---|
Widget |
Identity is runtimeType + key, which element reuse depends on. One comparing fields would break canUpdate |
package:flutter |
Mock |
Identity is the instance, which is what verify() matches on. One comparing fields would break verification |
package:mocktail, package:mockito |
Fake |
Implements an interface it does not hold the data for, so it inherits that interface’s == with no fields to compare |
package:test_api (re-exported by mocktail) |
Without these, every StatelessWidget in a Flutter project and every mock in its test tree would report.
The match is by supertype, so your own widget and mock base classes are covered without naming each one. Reach for additional_ignored_types rather than an exclude: ['test/**'] path: the exemption is a fact about those base classes, not about a directory, and a path exclusion would also hide a real equality bug in a test fixture or DTO.
Known limitations
Section titled “Known limitations”Fields are not counted. Any concrete subclass of an equality-overriding parent is reported, including one that adds only behaviour and no state. Such a class inherits the parent’s equality correctly, so silence it with // ignore: many_lints/prefer_overriding_parent_equality or re-declare the parent’s implementation.
Both halves must be present on the ancestor. A parent that overrides == but not hashCode does not trigger the rule on its children — though it is a bug in its own right, which the SDK’s hash_and_equals catches.
Each default is pinned to its declaring package. Mock and Fake are ordinary English words, so a domain class of the same name cannot silently exempt its whole subtree. Names you add through additional_ignored_types are matched by bare name, since a type declared in your own package has no package: URI to pin against.
Setting ignored_types replaces the defaults. Widgets and mocks start reporting again unless you re-list them; use additional_ignored_types to extend instead.
See also: Dart operator == and hashCode | Dart lint: hash_and_equals
Configuration
Section titled “Configuration”This rule is in the opinionated preset, so it is on with
preset: opinionated, or by name:
rules: prefer_overriding_parent_equality: trueTo turn it off again:
rules: prefer_overriding_parent_equality: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Options
Section titled “Options”many_lints: rules: prefer_overriding_parent_equality: additional_ignored_types: [Entity]rules: prefer_overriding_parent_equality: additional_ignored_types: [Entity]| Option | Type | Default | Description |
|---|---|---|---|
ignored_types |
list of strings | [Widget, Mock, Fake] |
Replaces the set of types whose subtypes are never reported. Setting it drops the defaults, so widgets and mocks report again |
additional_ignored_types |
list of strings | [] |
Extends the default set instead of replacing it, so Widget, Mock and Fake stay excluded |
Types are matched by supertype: naming a base class covers everything that extends or implements it.
Related rules
Section titled “Related rules”avoid_collection_equality_checks— Avoid comparing collections with == or != as it checks reference equality, not contents.list_all_equatable_fields— Ensure all fields are listed in Equatable props.prefer_equatable_mixin— Prefer using EquatableMixin instead of extending Equatable.avoid_accessing_collections_by_constant_index— Avoid accessing a collection by a constant index inside a loop.