toString() in Equatable and EquatableMixin silently overrides superclass implementations
- Dominant language
- Dart
- Stars
- 1.1k
- Forks
- 118
- PR merge metrics
- No merged PRs in 30d
Description
# Pull Request
https://github.com/felangel/equatable/pull/213
## Summary
Change the default `toString()` implementation in `equatable.dart` and `equatable_mixin.dart` to delegate to `super.toString()` instead of returning `'$runtimeType'` directly. For `EquatableMixin`, this allows classes that mix it in to correctly inherit `toString()` behavior from their superclass hierarchy. For `Equatable`, this is a consistency alignment with standard Dart behavior.
---
## Motivation
Currently, the default `toString()` implementation in both `Equatable` and `EquatableMixin` is:
```dart
@override
String toString() => '$runtimeType';
```
### `EquatableMixin`
Because `EquatableMixin` is a mixin, it can be applied to a class that already extends another class with a meaningful `toString()`. In this scenario, the mixin silently shadows that superclass implementation with a bare runtime type string without any visible indication in the consuming code.
This is a source of subtle, hard-to-detect bugs. The behavior changes as soon as `EquatableMixin` is added to the class declaration, and there is no way for the developer to notice unless they explicitly verify the output. Changing the implementation to `return super.toString()` restores the expected Dart method resolution order (MRO), allowing superclass `toString()` implementations to propagate correctly through the inheritance chain.
### `Equatable`
Since `Equatable` is an abstract class that does not extend any class other than `Object`, changing to `super.toString()` does not unlock any superclass inheritance benefit. The call simply resolves to `Object.toString()`, which returns `Instance of 'ClassName'` instead of the current `'$runtimeType'`. The motivation here is purely consistency aligning the behavior with standard Dart conventions rather than introducing a custom format.
---
## Proposed Change
```diff
- @override
- String toString() => '$runtimeType';
+ @override
+ String toString() => super.toString();
```
This change applies to both:
- `equatable.dart`
- `equatable_mixin.dart`
---
## Behavior Comparison (`EquatableMixin`)
### Before
```dart
import 'package:equatable/equatable.dart';
abstract class Animal {
const Animal();
@override
String toString() => 'THIS IS A ANIMAL';
}
class Dog extends Animal with EquatableMixin {
const Dog({required this.name});
final String name;
@override
List get props => [name];
}
void main() {
print(const Dog(name: 'Spot')); // 'Dog' ← superclass toString() is silently ignored
}
```
### After
```dart
import 'package:equatable/equatable.dart';
abstract class Animal {
const Animal();
@override
String toString() => 'THIS IS A ANIMAL';
}
class Dog extends Animal with EquatableMixin {
const Dog({required this.name});
final String name;
@override
List get props => [name];
}
void main() {
print(const Dog(name: 'Spot')); // 'THIS IS A ANIMAL' ← superclass toString() is now respected
}
```
Note that `Dog` itself is unchanged the output changes solely because `EquatableMixin` no longer silently swallows the superclass implementation.
---
## Impact on Existing Codebases
This is a **behavioral breaking change** for any class that:
1. Mixes in `EquatableMixin`, **and**
2. Does **not** override `toString()` itself, **and**
3. Inherits a non-default `toString()` from a superclass.
Additionally, any class extending `Equatable` or mixing in `EquatableMixin` that relies on the current `'$runtimeType'` string format will observe a change to `Instance of 'ClassName'`. Teams are advised to audit usages of `.toString()` (including implicit calls via string interpolation or `print`) on classes that extend or mix in equatable abstractions.
---
## Notes
- Classes that already define their own `toString()` override are most likely **unaffected**.
- The superclass propagation benefit described above applies **exclusively to `EquatableMixin`**, as it is the only one that can be combined with an independent class hierarchy.
- This change aligns `equatable` with the principle of least surprise and the expected Dart method resolution order.
Contributor guide
Assessment
This issue has not been assessed yet.