Background
C# `record` types lower to TS classes whose `equals(other)` method compares every field with strict equality (`this.x === other.x`). For primitive fields this matches C# semantics; for value-wrapper types like `decimal.js` `Decimal` (the BCL mapping for C# `decimal`) and Temporal types it does not — two numerically equal `Decimal` instances are different objects, so `equals` returns `false` even when the records would compare equal in C#.
Surfaced on `SampleQueryableSqlite.Product` (#201) — `Product.UnitPrice` is a `decimal` field; the generated `equals` compares the two `Decimal` instances by reference and silently diverges from .NET semantics. The same wrapper hits Temporal types (`PlainDate`, `PlainDateTime`, `Duration`) once those flow through a record.
Proposal
For each record field, the bridge should pick the comparison strategy from the field's lowered type:
- Primitive (number / string / bigint / boolean): `===`.
- Value-wrapper with a known `equals`-shaped method (Decimal, Temporal, HashSet/HashMap, our own `[Branded]`/`[InlineWrapper]` types): call `x.equals(y)` (or the type-specific helper, e.g. `Decimal.eq(a, b)`, `Temporal.PlainDate.compare(a, b) === 0`).
- Reference type: keep `===` (current behavior — matches reference-equality for nullable record fields).
The bridge already inspects field types at lowering time (`IrToTsExpressionBridge` / record handler); the comparison choice can fold into the same dispatch.
Scope
Workaround until this lands
Override `equals` / `GetHashCode` manually on records carrying value-wrapper fields. For DTOs that flow through JSON, the issue is moot because the wire shape is plain — only in-memory comparisons hit the bug.
References
Background
C# `record` types lower to TS classes whose `equals(other)` method compares every field with strict equality (`this.x === other.x`). For primitive fields this matches C# semantics; for value-wrapper types like `decimal.js` `Decimal` (the BCL mapping for C# `decimal`) and Temporal types it does not — two numerically equal `Decimal` instances are different objects, so `equals` returns `false` even when the records would compare equal in C#.
Surfaced on `SampleQueryableSqlite.Product` (#201) — `Product.UnitPrice` is a `decimal` field; the generated `equals` compares the two `Decimal` instances by reference and silently diverges from .NET semantics. The same wrapper hits Temporal types (`PlainDate`, `PlainDateTime`, `Duration`) once those flow through a record.
Proposal
For each record field, the bridge should pick the comparison strategy from the field's lowered type:
The bridge already inspects field types at lowering time (`IrToTsExpressionBridge` / record handler); the comparison choice can fold into the same dispatch.
Scope
Workaround until this lands
Override `equals` / `GetHashCode` manually on records carrying value-wrapper fields. For DTOs that flow through JSON, the issue is moot because the wire shape is plain — only in-memory comparisons hit the bug.
References