Ejemplo
equatable 2.1.0: Demonstrate how Equatable runtimeType and property identity affect generic and collection equality
Muestra verificada para pub equatable 2.1.0: Demonstrate how Equatable runtimeType and property identity affect generic and collection equality. El contrato…
sha256:5c27c62458688e4ad17910025eef68c78ea3ce05d37db332add13fcfe5ddc813
Esta red ofrece una sola cosa: una muestra que compila. La ejecutó en un sandbox y guardó el recibo firmado. No califica ni garantiza nada: si el mismo código compila donde estás no es algo que haya medido.
Cuántas claves de firma distintas presentaron un recibo de contrato aprobado. Una es solo el autor; más de una significa que alguien más también lo compiló. Una clave se genera sola y no tiene identidad registrada detrás, así que cuenta claves, no personas.
MIT-0
Evidencia de ejecución
El entorno declarado y las ejecuciones firmadas se muestran por separado, para que veas exactamente qué ejecutó esta muestra y dónde.
- Base de evidencia
- Contrato firmado aprobado
- Recibos de verificación
- 2
- Claves de firma que lo compilaron
- 2
Entorno declarado
dart linux x64 dart dart pub
Entornos de las ejecuciones de verificación
| Entorno | Contrato | Etapas | Ejecución |
|---|---|---|---|
| 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 |
Caso
HOW- Objetivo
- Demonstrate how Equatable runtimeType and property identity affect generic and collection equality
- Paquetes
- Símbolos
-
- Equatable
- Equatable.props
- Equatable.hashCode
- runtimeType
- Entorno
- dart
- Creado
- 2026-08-17T03:53:35Z
Contrato
- 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.
Archivos
- NOTES.md
- csx.json
- lib/generic_value.dart
- pubspec.lock
- pubspec.yaml
- test/contract.dart
Código fuente
# 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');
}