Skip to content
Merged

Fix == #1224

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/freezed/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## Unreleased fix

- Fix ==/hashCode when using inheritance.
The generated ==/hashCode now call `super == other` when necessary.

## 3.0.4 - 2025-03-16

- Update docs (thanks to @lishaduck)
Expand Down
34 changes: 18 additions & 16 deletions packages/freezed/lib/src/ast.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';

extension AstX on AstNode {
String? get documentation {
Expand Down Expand Up @@ -37,23 +38,24 @@ extension ClassX on ClassDeclaration {
return false;
}

bool get hasCustomEquals {
final element = declaredElement!;
bool get hasSuperEqual => declaredElement!.allSupertypes
.where((e) => !e.isDartCoreObject)
.map((e) => e.element)
.any((e) => e.hasEqual);

for (final type in [
element,
...element.allSupertypes
.where((e) => !e.isDartCoreObject)
.map((e) => e.element),
]) {
for (final method in type.methods.where((e) => e.isOperator)) {
if (method.name == '==') {
return true;
}
}
}
return false;
}
bool get hasCustomEquals => declaredElement!.hasEqual;

bool get hasSuperHashCode => declaredElement!.allSupertypes
.where((e) => !e.isDartCoreObject)
.map((e) => e.element)
.any((e) => e.hasHashCode);
}

extension on InterfaceElement {
bool get hasEqual => methods.any(((e) => e.isOperator && e.name == '=='));

bool get hasHashCode =>
accessors.where((e) => e.isGetter).any((e) => e.name == 'hashCode');
}

extension ConstructorX on ConstructorDeclaration {
Expand Down
3 changes: 3 additions & 0 deletions packages/freezed/lib/src/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,9 @@ class Class {
}
}

bool get hasSuperEqual => _node.hasSuperEqual;
bool get hasSuperHashCode => _node.hasSuperHashCode;

String get escapedName {
var generics =
genericsParameterTemplate.typeParameters.map((e) => '\$$e').join(', ');
Expand Down
2 changes: 2 additions & 0 deletions packages/freezed/lib/src/templates/concrete_template.dart
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ String operatorEqualMethod(
final comparisons = [
'other.runtimeType == runtimeType',
'other is $className${data.genericsParameterTemplate}',
if (data.hasSuperEqual) 'super == other',
...properties.map((p) {
var name = p.name;

Expand Down Expand Up @@ -469,6 +470,7 @@ String hashCodeMethod(

final hashedProperties = [
'runtimeType',
if (data.hasSuperHashCode) 'super.hashCode',
for (final property in properties)
if (property.isPossiblyDartCollection)
if (data.options.asUnmodifiableCollections &&
Expand Down
15 changes: 15 additions & 0 deletions packages/freezed/test/extend_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:test/test.dart';

import 'integration/extend.dart';

void main() {
test('Calls super', () {
expect(Subclass2(1, 2), Subclass2(1, 2));
expect(Subclass2(1, 1), isNot(Subclass2(1, 2)));
expect(Subclass2(2, 2), isNot(Subclass2(1, 2)));

expect(Subclass2(1, 2).hashCode, Subclass2(1, 2).hashCode);
expect(Subclass2(1, 1).hashCode, isNot(Subclass2(1, 2).hashCode));
expect(Subclass2(2, 2).hashCode, isNot(Subclass2(1, 2).hashCode));
});
}
55 changes: 55 additions & 0 deletions packages/freezed/test/integration/extend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,58 @@ abstract class Subclass extends Base with _$Subclass {
const Subclass._(super.value) : super.named();
const factory Subclass(int value) = _Subclass;
}

class BaseWithEqual {
const BaseWithEqual(this.another);
final int another;

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! BaseWithEqual) return false;
return another == other.another;
}

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

@freezed
class Subclass2 extends BaseWithEqual with _$Subclass2 {
Subclass2(this.value, super.another) : super();

@override
final int value;
}

class EqualWithoutHashCode {
@override
// ignore: hash_and_equals
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! EqualWithoutHashCode) return false;
return true;
}
}

class HashCodeWithoutEqual {
@override
// ignore: hash_and_equals
int get hashCode => 42;
}

@freezed
class Subclass3 extends EqualWithoutHashCode with _$Subclass3 {
Subclass3(this.value) : super();

@override
final int value;
}

@freezed
class Subclass4 extends HashCodeWithoutEqual with _$Subclass4 {
Subclass4(this.value) : super();

@override
final int value;
}