샘플
equatable 2.1.0: Demonstrate how Equatable runtimeType and property identity affect generic and collection equality
검증된 샘플 — pub equatable 2.1.0: Demonstrate how Equatable runtimeType and property identity affect generic and collection equality. dart 3 · linux debian/x64 …
sha256:5c27c62458688e4ad17910025eef68c78ea3ce05d37db332add13fcfe5ddc813
이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다.
통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다.
MIT-0
실행 증거
선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.
- 증거 기준
- 서명된 컨트랙트 통과
- 검증 영수증
- 2
- 빌드한 서명 키
- 2
선언된 환경
dart linux x64 dart dart pub
검증 실행 환경
| 환경 | 컨트랙트 | 단계 | 실행일 |
|---|---|---|---|
| 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 |
케이스
HOW- 목표
- Demonstrate how Equatable runtimeType and property identity affect generic and collection equality
- 패키지
- 심벌
-
- Equatable
- Equatable.props
- Equatable.hashCode
- runtimeType
- 환경
- dart
- 생성일
- 2026-08-17T03:53:35Z
컨트랙트
- 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.
파일
- NOTES.md
- csx.json
- lib/generic_value.dart
- pubspec.lock
- pubspec.yaml
- test/contract.dart
소스
# 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');
}