CodeSampleX

Exemple

equatable 2.1.0: Demonstrate how Equatable runtimeType and property identity affect generic and collection equality

Échantillon vérifié pour pub equatable 2.1.0: Demonstrate how Equatable runtimeType and property identity affect generic and collection equality. Le contrat…

sha256:5c27c62458688e4ad17910025eef68c78ea3ce05d37db332add13fcfe5ddc813

Ce réseau offre une seule chose : un échantillon qui compile. Il l'a exécuté dans un bac à sable et conservé le reçu signé. Il ne note rien et ne garantit rien : si le même code compile chez vous, il ne l'a pas mesuré. Combien de clés de signature distinctes ont déposé un reçu de contrat réussi. Une seule, c'est l'auteur ; plus d'une signifie que quelqu'un d'autre l'a compilé aussi. Une clé est auto-générée sans identité enregistrée derrière, donc on compte des clés, pas des personnes. MIT-0

Preuves d'exécution

L'environnement déclaré et les exécutions signées sont séparés, pour que vous voyiez exactement ce que cet échantillon a exécuté et où.

Base de preuve
Contrat signé réussi
Reçus de vérification
2
Clés de signature qui l’ont compilé
2
Environnement déclaré dart linux x64 dart dart pub

Environnements des exécutions de vérification

Environnement Contrat Étapes Exécution
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

Cas

HOW
Objectif
Demonstrate how Equatable runtimeType and property identity affect generic and collection equality
Paquets
Symboles
  • Equatable
  • Equatable.props
  • Equatable.hashCode
  • runtimeType
Environnement
dart
Créé
2026-08-17T03:53:35Z

Contrat

  1. Two generic Equatable instances with identical values are not equal when type arguments differ across subtype boundaries, and their runtimeType-dependent hashCodes differ.
  2. Box<Object?> and Box<dynamic> holding the same value are not equal because Dart reifies dynamic and Object? as distinct runtime types.
  3. Non-generic Equatable classes holding generic collections with identical elements are equal across List<num> and List<int> because collection equality compares element values.
  4. Two distinct holders of the shared double.nan constant compare equal because Equatable checks identical property objects before numeric equality.
  5. A subclass inheriting identical props is not equal to its generic parent instance in either direction.

Fichiers

  • NOTES.md
  • csx.json
  • lib/generic_value.dart
  • pubspec.lock
  • pubspec.yaml
  • test/contract.dart

Télécharger l’artefact source (tar.gz)

Code source

NOTES.md
# 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.
csx.json
{"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"}
lib/generic_value.dart
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];
}
pubspec.lock
# 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"
pubspec.yaml
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
test/contract.dart
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');
}

Seeder d'origine

csx-seed