API Stability Notice
Macroforge is under active development. The API is not yet stable and may change between versions. Some documentation sections may be outdated.
PartialEq
The PartialEq macro generates an equals() method for field-by-field
structural equality comparison. This is analogous to Rust’s PartialEq trait,
enabling value-based equality semantics instead of reference equality.
Generated Output
| Type | Generated Code | Description |
|---|---|---|
| Class | classNameEquals(a, b) + static equals(a, b) | Standalone function + static wrapper method |
| Enum | enumNameEquals(a: EnumName, b: EnumName): boolean | Standalone function using strict equality |
| Interface | interfaceNameEquals(a: InterfaceName, b: InterfaceName): boolean | Standalone function comparing fields |
| Type Alias | typeNameEquals(a: TypeName, b: TypeName): boolean | Standalone function with type-appropriate comparison |
Comparison Strategy
The generated equality check:
- Identity check:
a === breturns true immediately - Field comparison: Compares each non-skipped field
Type-Specific Comparisons
| Type | Comparison Method |
|---|---|
| Primitives | Strict equality (===) |
| Arrays | Length + element-by-element (recursive) |
Date | getTime() comparison |
Map | Size + entry-by-entry comparison |
Set | Size + membership check |
| Objects | Calls equals() if available, else === |
Field-Level Options
The @partialEq decorator supports:
skip- Exclude the field from equality comparison
Example
Before (Your Code)
/** @derive(PartialEq, Hash) */
class User {
id: number;
name: string;
/** @partialEq({ skip: true }) @hash({ skip: true }) */
cachedScore: number;
} After (Generated)
class User {
id: number;
name: string;
cachedScore: number;
static equals(a: User, b: User): boolean {
return userEquals(a, b);
}
static hashCode(value: User): number {
return userHashCode(value);
}
}
export function userEquals(a: User, b: User): boolean {
if (a === b) return true;
return a.id === b.id && a.name === b.name;
}
export function userHashCode(value: User): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(value.id)
? value.id | 0
: value.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(value.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
return hash;
}Generated output:
class User {
id: number;
name: string;
cachedScore: number;
static equals(a: User, b: User): boolean {
return userEquals(a, b);
}
static hashCode(value: User): number {
return userHashCode(value);
}
}
export function userEquals(a: User, b: User): boolean {
if (a === b) return true;
return a.id === b.id && a.name === b.name;
}
export function userHashCode(value: User): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(value.id)
? value.id | 0
: value.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(value.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
return hash;
}Equality Contract
When implementing PartialEq, consider also implementing Hash:
- Reflexivity:
a.equals(a)is always true - Symmetry:
a.equals(b)impliesb.equals(a) - Hash consistency: Equal objects must have equal hash codes
To maintain the hash contract, skip the same fields in both PartialEq and Hash:
Before (Your Code)
/** @derive(PartialEq, Hash) */
class User {
id: number;
name: string;
/** @partialEq({ skip: true }) @hash({ skip: true }) */
cachedScore: number;
} After (Generated)
class User {
id: number;
name: string;
cachedScore: number;
static equals(a: User, b: User): boolean {
return userEquals(a, b);
}
static hashCode(value: User): number {
return userHashCode(value);
}
}
export function userEquals(a: User, b: User): boolean {
if (a === b) return true;
return a.id === b.id && a.name === b.name;
}
export function userHashCode(value: User): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(value.id)
? value.id | 0
: value.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(value.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
return hash;
}Generated output:
class User {
id: number;
name: string;
cachedScore: number;
static equals(a: User, b: User): boolean {
return userEquals(a, b);
}
static hashCode(value: User): number {
return userHashCode(value);
}
}
export function userEquals(a: User, b: User): boolean {
if (a === b) return true;
return a.id === b.id && a.name === b.name;
}
export function userHashCode(value: User): number {
let hash = 17;
hash =
(hash * 31 +
(Number.isInteger(value.id)
? value.id | 0
: value.id
.toString()
.split('')
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
0;
hash =
(hash * 31 +
(value.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
0;
return hash;
}