Beispiel
equatable 2.1.0: Demonstrate how Equatable runtimeType and property identity affect generic and collection equality
Verifiziertes Beispiel für pub equatable 2.1.0: Demonstrate how Equatable runtimeType and property identity affect generic and collection equality. Der…
sha256:5c27c62458688e4ad17910025eef68c78ea3ce05d37db332add13fcfe5ddc813
Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen.
Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen.
MIT-0
Ausführungsbelege
Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.
- Beleggrundlage
- Signierter Vertrag bestanden
- Verifizierungsbelege
- 2
- Signaturschlüssel, die es gebaut haben
- 2
Deklarierte Umgebung
dart linux x64 dart dart pub
Umgebungen der Verifizierungsläufe
| Umgebung | Contract | Stufen | Lauf |
|---|---|---|---|
| dart 3 · linux debian/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · pub@1 |
2026-08-17 |
| dart 3 · linux debian/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · pub@1 |
2026-08-18 |
Fall
HOW- Ziel
- Demonstrate how Equatable runtimeType and property identity affect generic and collection equality
- Pakete
- Symbole
-
- Equatable
- Equatable.props
- Equatable.hashCode
- runtimeType
- Umgebung
- dart
- Erstellt
- 2026-08-17T03:53:35Z
Contract
- Two generic Equatable instances with identical values are not equal when type arguments differ across subtype boundaries, and their runtimeType-dependent hashCodes differ.
- Box<Object?> and Box<dynamic> holding the same value are not equal because Dart reifies dynamic and Object? as distinct runtime types.
- Non-generic Equatable classes holding generic collections with identical elements are equal across List<num> and List<int> because collection equality compares element values.
- Two distinct holders of the shared double.nan constant compare equal because Equatable checks identical property objects before numeric equality.
- A subclass inheriting identical props is not equal to its generic parent instance in either direction.
Dateien
- NOTES.md
- csx.json
- lib/generic_value.dart
- pubspec.lock
- pubspec.yaml
- test/contract.dart
Quelltext
# Equatable Generic Type Arguments and RuntimeType Equality
## Prior Findings
A query for `equatable` solutions identified sample `sha256:89d2bef982143e36175a1174a40d2ddf617e91bf571cde4cd217db418fbf1a1b`, which focused on value equality versus identity, mutable props in Set/Map collections, and hand-written `copyWith` patterns for nullable fields. This sample investigates a distinct trap: how `Equatable`'s `runtimeType` equality check interacts with generic type arguments (`Box<num>` vs `Box<int>`), `Object?` versus `dynamic` reification, and the contrasting behavior where nested generic collections (`List<num>` vs `List<int>`) inside non-generic classes are compared value-wise and treated as equal.
## Failure Mode
When code assumes `Box<num>(42) == Box<int>(42)` holds due to value equality, the equality check fails silently returning `false`, causing lookups in `Set<Box<dynamic>>` or `Map<Box<dynamic>, V>` to miss stored keys and silently accumulate duplicate entries.
{"case":{"believed":"Two generic Equatable instances with identical values are equal when one type argument is a subtype of the other, such as Box\u003cnum\u003e(42) and Box\u003cint\u003e(42).","caseId":"case:sha256:98436c409d3f9b8e449625723292e2b5f2653d92fc0a906f4362ba9731f7e291","contract":["Two generic Equatable instances with identical values are not equal when type arguments differ across subtype boundaries, and their runtimeType-dependent hashCodes differ.","Box\u003cObject?\u003e and Box\u003cdynamic\u003e holding the same value are not equal because Dart reifies dynamic and Object? as distinct runtime types.","Non-generic Equatable classes holding generic collections with identical elements are equal across List\u003cnum\u003e and List\u003cint\u003e because collection equality compares element values.","Two distinct holders of the shared double.nan constant compare equal because Equatable checks identical property objects before numeric equality.","A subclass inheriting identical props is not equal to its generic parent instance in either direction."],"goal":"Demonstrate how Equatable runtimeType and property identity affect generic and collection equality","kind":"HOW","packages":["pkg:pub/equatable@2.1.0"],"schemaVersion":1,"symbols":["Equatable","Equatable.props","Equatable.hashCode","runtimeType"]},"contractCommand":["dart","test/contract.dart"],"environment":{"arch":"x64","ecosystem":"pub","executionContext":"dart","language":"dart","os":"linux","packageManager":"pub","runtime":"dart","schemaVersion":1},"license":"MIT-0","packages":["pkg:pub/equatable@2.1.0"],"schemaVersion":1,"symbols":["Equatable","Equatable.props","Equatable.hashCode","runtimeType"],"verifierAdapter":"pub@1"}
import 'package:equatable/equatable.dart';
/// A generic value container using Equatable.
///
/// Because Equatable's equality operator checks `runtimeType == other.runtimeType`
/// before evaluating `props`, two instances of [Box] with different type arguments
/// (e.g. `Box<num>` and `Box<int>`) will never be equal, even if the wrapped
/// values are identical.
class Box<T> extends Equatable {
const Box(this.value);
final T value;
@override
List<Object?> get props => [value];
}
/// A subclass extending [Box] without adding fields.
///
/// Subclassing changes the `runtimeType`, so `SpecialBox<int>` and `Box<int>`
/// will never compare equal despite identical type arguments and values.
class SpecialBox<T> extends Box<T> {
const SpecialBox(super.value);
}
/// A non-generic class holding a generic collection.
///
/// In contrast to generic classes, Equatable's collection equality compares
/// elements deeply and value-wise without checking the `runtimeType` of the collection.
class NumberListHolder extends Equatable {
const NumberListHolder(this.numbers);
final List<num> numbers;
@override
List<Object?> get props => [numbers];
}
/// A class holding a numeric value to demonstrate IEEE 754 NaN and identity behavior.
class NumericHolder extends Equatable {
const NumericHolder(this.value);
final double value;
@override
List<Object?> get props => [value];
}
/// A class holding a Dart record property.
class RecordHolder extends Equatable {
const RecordHolder(this.record);
final ({int id, String name}) record;
@override
List<Object?> get props => [record];
}
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
equatable:
dependency: "direct main"
description:
name: equatable
sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
meta:
dependency: transitive
description:
name: meta
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
url: "https://pub.dev"
source: hosted
version: "1.19.0"
sdks:
dart: ">=3.5.0 <4.0.0"
name: equatable_generic_equality
description: Proves Equatable runtimeType and generic type argument behavior.
publish_to: none
environment:
sdk: '>=3.5.0 <4.0.0'
dependencies:
equatable: 2.1.0
import 'package:equatable_generic_equality/generic_value.dart';
void check(bool condition, String message) {
if (!condition) {
throw StateError('Assertion failed: $message');
}
}
void main() {
// 1. Generic type argument subtyping: Box<num> vs Box<int>
// Even though 42 == 42 and int <: num in Dart's type hierarchy,
// Equatable verifies runtimeType == other.runtimeType before evaluating props.
final Box<num> numBox = Box<num>(42);
final Box<int> intBox = Box<int>(42);
check(numBox != intBox, 'Box<num>(42) must not equal Box<int>(42)');
check(intBox != numBox, 'Box<int>(42) must not equal Box<num>(42)');
check(
numBox.hashCode != intBox.hashCode,
'Box<num>(42) and Box<int>(42) must have different hashCodes',
);
// Set lookup fails across reified type arguments:
final set = <Box<dynamic>>{numBox};
check(set.contains(numBox), 'Set must contain stored Box<num>');
check(!set.contains(intBox), 'Set<Box<dynamic>> must not match Box<int>');
check(set.length == 1, 'Set initial length should be 1');
set.add(intBox);
check(
set.length == 2,
'Set should hold both Box<num> and Box<int> as distinct entries',
);
// Map key lookup fails across reified type arguments:
final map = <Box<dynamic>, String>{numBox: 'stored_num'};
check(map[numBox] == 'stored_num', 'Map must retrieve value by Box<num>');
check(map[intBox] == null, 'Map lookup by Box<int> must return null');
check(!map.containsKey(intBox), 'Map must not contain Box<int> key');
// 2. Object? vs dynamic reification
final Box<Object?> objBox = Box<Object?>(42);
final Box<dynamic> dynBox = Box<dynamic>(42);
check(
objBox != dynBox,
'Box<Object?>(42) must not equal Box<dynamic>(42)',
);
check(
objBox.hashCode != dynBox.hashCode,
'Box<Object?> and Box<dynamic> must have different hashCodes',
);
// 3. Collection generic types vs Class generic types (The Contrast)
// Non-generic classes holding generic collections with different type arguments
// ARE equal because collection equality compares elements deeply and ignores
// collection runtimeType.
final listNum = <num>[1, 2, 3];
final listInt = <int>[1, 2, 3];
final holder1 = NumberListHolder(listNum);
final holder2 = NumberListHolder(listInt);
check(
holder1 == holder2,
'NumberListHolder holding List<num> must equal NumberListHolder holding List<int>',
);
check(
holder1.hashCode == holder2.hashCode,
'NumberListHolder instances with identical elements must share hashCode',
);
final holderSet = {holder1};
check(
holderSet.contains(holder2),
'Set must find holder2 containing List<int> when holder1 was stored',
);
holderSet.add(holder2);
check(
holderSet.length == 1,
'Set must deduplicate holder1 and holder2 based on value equality',
);
// 4. Identity short-circuit and NaN behavior
final nan1 = NumericHolder(double.nan);
final nan2 = NumericHolder(double.nan);
// double.nan is a shared constant. Equatable's property comparison checks
// identity before numeric equality, so distinct holders compare equal.
check(
nan1 == nan2,
'Distinct holders of the shared double.nan constant must compare equal',
);
check(
nan1.hashCode == nan2.hashCode,
'Equal NumericHolder instances must have identical hashCodes',
);
// identical() check in Equatable.operator== short-circuits before props/runtimeType
check(
nan1 == nan1,
'NumericHolder instance must equal itself via identity short-circuit',
);
// 5. Subclassing and inheritance
final parentBox = Box<int>(42);
final childBox = SpecialBox<int>(42);
check(
parentBox != childBox,
'Parent Box<int> must not equal SpecialBox<int>',
);
check(
childBox != parentBox,
'SpecialBox<int> must not equal Box<int>',
);
check(
parentBox.hashCode != childBox.hashCode,
'SpecialBox<int> and Box<int> must produce different hashCodes',
);
// 6. Record structural equality
final r1 = RecordHolder((id: 1, name: 'alpha'));
final r2 = RecordHolder((id: 1, name: 'alpha'));
final r3 = RecordHolder((id: 2, name: 'alpha'));
check(r1 == r2, 'RecordHolder with matching records must be equal');
check(r1.hashCode == r2.hashCode, 'RecordHolder hashCodes must match');
check(r1 != r3, 'RecordHolder with differing records must not be equal');
}