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.
Hash
The Hash macro generates a hashCode() method for computing numeric hash codes.
This is analogous to Rust’s Hash trait and Java’s hashCode() method, enabling
objects to be used as keys in hash-based collections.
Generated Output
| Type | Generated Code | Description |
|---|---|---|
| Class | classNameHashCode(value) + static hashCode(value) | Standalone function + static wrapper method |
| Enum | enumNameHashCode(value: EnumName): number | Standalone function hashing by enum value |
| Interface | interfaceNameHashCode(value: InterfaceName): number | Standalone function computing hash |
| Type Alias | typeNameHashCode(value: TypeName): number | Standalone function computing hash |
Hash Algorithm
Uses the standard polynomial rolling hash algorithm:
hash = 17 // Initial seed
for each field:
hash = (hash * 31 + fieldHash) | 0 // Bitwise OR keeps it 32-bit integerThis algorithm is consistent with Java’s Objects.hash() implementation.
Type-Specific Hashing
| Type | Hash Strategy |
|---|---|
number | Integer: direct value; Float: string hash of decimal |
bigint | String hash of decimal representation |
string | Character-by-character polynomial hash |
boolean | 1231 for true, 1237 for false (Java convention) |
Date | getTime() timestamp |
| Arrays | Element-by-element hash combination |
Map | Entry-by-entry key+value hash |
Set | Element-by-element hash |
| Objects | Calls hashCode() if available, else JSON string hash |
Field-Level Options
The @hash decorator supports:
skip- Exclude the field from hash calculation
Example
Before (Your Code)
/** @derive(Hash, PartialEq) */
class User {
id: number;
name: string;
/** @hash({ skip: true }) */
cachedScore: number;
} After (Generated)
class User {
id: number;
name: string;
cachedScore: number;
static hashCode(value: User): number {
return userHashCode(value);
}
static equals(a: User, b: User): boolean {
return userEquals(a, b);
}
}
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;
}
export function userEquals(a: User, b: User): boolean {
if (a === b) return true;
return a.id === b.id && a.name === b.name && a.cachedScore === b.cachedScore;
}Generated output:
class User {
id: number;
name: string;
cachedScore: number;
static hashCode(value: User): number {
return userHashCode(value);
}
static equals(a: User, b: User): boolean {
return userEquals(a, b);
}
}
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;
}
export function userEquals(a: User, b: User): boolean {
if (a === b) return true;
return a.id === b.id && a.name === b.name && a.cachedScore === b.cachedScore;
}Hash Contract
Objects that are equal (PartialEq) should produce the same hash code.
When using @hash(skip), ensure the same fields are skipped in both Hash and PartialEq to maintain this contract.