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

TypeGenerated CodeDescription
ClassclassNameHashCode(value) + static hashCode(value)Standalone function + static wrapper method
EnumenumNameHashCode(value: EnumName): numberStandalone function hashing by enum value
InterfaceinterfaceNameHashCode(value: InterfaceName): numberStandalone function computing hash
Type AliastypeNameHashCode(value: TypeName): numberStandalone function computing hash

Hash Algorithm

Uses the standard polynomial rolling hash algorithm:

hash = 17  // Initial seed
for each field:
    hash = (hash * 31 + fieldHash) | 0

The | 0 (bitwise OR with zero) at the end of each step coerces the result to a 32-bit signed integer, preventing floating-point drift from repeated multiplication. This is equivalent to casting to i32 in Rust and consistent with Java’s Objects.hash() implementation.

Type-Specific Hashing

TypeHash Strategy
numberInteger: direct value; Float: string hash of decimal
bigintString hash of decimal representation
stringCharacter-by-character polynomial hash
boolean1231 for true, 1237 for false (Java convention)
DategetTime() timestamp
ArraysElement hashes combined in order
Map, SetEntry hashes summed, since equality ignores their order
Optional and nullable0 when absent
Types deriving HashTheir hashCode function
Anything elsestructuralHash from @macroforge/core/structural

Values equal under the derived PartialEq always hash the same.

Field-Level Options

The @hash decorator supports:

  • skip - Exclude the field from hash calculation

Example

Before (Your Code)
/** @derive(Hash) */
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);
    }
}

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;
}

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.