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.

PartialOrd

The PartialOrd macro generates a compareTo() method for partial ordering comparison. This is analogous to Rust’s PartialOrd trait, enabling comparison between values where some pairs may be incomparable.

Generated Output

TypeGenerated CodeDescription
ClassclassNamePartialCompare(a, b) + static compareTo(a, b)Standalone function + static wrapper method
EnumenumNamePartialCompare(a: EnumName, b: EnumName): Option<number>Standalone function returning Option
InterfaceinterfaceNamePartialCompare(a: InterfaceName, b: InterfaceName): Option<number>Standalone function with Option
Type AliastypeNamePartialCompare(a: TypeName, b: TypeName): Option<number>Standalone function with Option

Return Values

Unlike Ord, PartialOrd returns an Option<number> to handle incomparable values:

  • Option.some(-1): a is less than b
  • Option.some(0): a is equal to b
  • Option.some(1): a is greater than b
  • Option.none(): Values are incomparable

When to Use PartialOrd vs Ord

  • PartialOrd: When some values may not be comparable

    • Example: Floating-point NaN values
    • Example: Mixed-type unions
    • Example: Type mismatches between objects
  • Ord: When all values are guaranteed comparable (total ordering)

Comparison Strategy

Fields are compared lexicographically in declaration order:

  1. Compare first field
  2. If incomparable, return Option.none()
  3. If not equal, return that result wrapped in Option.some()
  4. Otherwise, compare next field
  5. Continue until a difference is found or all fields are equal

Type-Specific Comparisons

TypeComparison Method
number/bigintDirect comparison, returns some()
stringlocaleCompare() wrapped in some()
booleanfalse < true, wrapped in some()
null/undefinedReturns none() for mismatched nullability
ArraysLexicographic, propagates none() on incomparable elements
DateTimestamp comparison, none() if invalid
ObjectsUnwraps nested Option from compareTo()

Field-Level Options

The @ord decorator supports:

  • skip - Exclude the field from ordering comparison

Example

Before (Your Code)
/** @derive(PartialOrd) */
class Temperature {
    value: number | null;
    unit: string;
}
After (Generated)
class Temperature {
    value: number | null;
    unit: string;

    static compareTo(a: Temperature, b: Temperature): number | null {
        return temperaturePartialCompare(a, b);
    }
}

export function temperaturePartialCompare(a: Temperature, b: Temperature): number | null {
    if (a === b) return 0;
    const cmp0 = (() => {
        if (typeof (a.value as any)?.compareTo === 'function') {
            const optResult = (a.value as any).compareTo(b.value);
            return optResult === null ? null : optResult;
        }
        return a.value === b.value ? 0 : null;
    })();
    if (cmp0 === null) return null;
    if (cmp0 !== 0) return cmp0;
    const cmp1 = a.unit.localeCompare(b.unit);
    if (cmp1 === null) return null;
    if (cmp1 !== 0) return cmp1;
    return 0;
}

Generated output:

class Temperature {
    value: number | null;
    unit: string;

    static compareTo(a: Temperature, b: Temperature): number | null {
        return temperaturePartialCompare(a, b);
    }
}

export function temperaturePartialCompare(a: Temperature, b: Temperature): number | null {
    if (a === b) return 0;
    const cmp0 = (() => {
        if (typeof (a.value as any)?.compareTo === 'function') {
            const optResult = (a.value as any).compareTo(b.value);
            return optResult === null ? null : optResult;
        }
        return a.value === b.value ? 0 : null;
    })();
    if (cmp0 === null) return null;
    if (cmp0 !== 0) return cmp0;
    const cmp1 = a.unit.localeCompare(b.unit);
    if (cmp1 === null) return null;
    if (cmp1 !== 0) return cmp1;
    return 0;
}

Required Import

The generated code automatically adds an import for Option from macroforge/utils.