From 385aa921804824a848b8a5c67f08f100573156c1 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Sat, 5 Apr 2025 11:47:18 +0200 Subject: [PATCH 1/2] Call super.hashCode/super.equal when present --- packages/freezed/lib/src/ast.dart | 34 ++++++------ packages/freezed/lib/src/models.dart | 3 + .../lib/src/templates/concrete_template.dart | 2 + packages/freezed/test/extend_test.dart | 15 +++++ packages/freezed/test/integration/extend.dart | 55 +++++++++++++++++++ 5 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 packages/freezed/test/extend_test.dart diff --git a/packages/freezed/lib/src/ast.dart b/packages/freezed/lib/src/ast.dart index dc24bcfa..699dae2b 100644 --- a/packages/freezed/lib/src/ast.dart +++ b/packages/freezed/lib/src/ast.dart @@ -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 { @@ -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 { diff --git a/packages/freezed/lib/src/models.dart b/packages/freezed/lib/src/models.dart index 535037c9..bd0897f0 100644 --- a/packages/freezed/lib/src/models.dart +++ b/packages/freezed/lib/src/models.dart @@ -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(', '); diff --git a/packages/freezed/lib/src/templates/concrete_template.dart b/packages/freezed/lib/src/templates/concrete_template.dart index 96ea6f9e..fc5a3f5d 100644 --- a/packages/freezed/lib/src/templates/concrete_template.dart +++ b/packages/freezed/lib/src/templates/concrete_template.dart @@ -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; @@ -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 && diff --git a/packages/freezed/test/extend_test.dart b/packages/freezed/test/extend_test.dart new file mode 100644 index 00000000..1d625cff --- /dev/null +++ b/packages/freezed/test/extend_test.dart @@ -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)); + }); +} diff --git a/packages/freezed/test/integration/extend.dart b/packages/freezed/test/integration/extend.dart index b98a6f73..ca57ec2b 100644 --- a/packages/freezed/test/integration/extend.dart +++ b/packages/freezed/test/integration/extend.dart @@ -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; +} From cf3aea444909d3c341d99fac9759a9527149f6fa Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Sat, 5 Apr 2025 11:50:44 +0200 Subject: [PATCH 2/2] Changelog --- packages/freezed/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/freezed/CHANGELOG.md b/packages/freezed/CHANGELOG.md index 779b89ae..c9b68f52 100644 --- a/packages/freezed/CHANGELOG.md +++ b/packages/freezed/CHANGELOG.md @@ -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)