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.
Newtype
$Newtype<T> is a type-position macro that gives T a nominal brand. Two
newtypes over the same base are distinct types, and neither accepts a bare T without going through a constructor or guard.
Generated Output
export type Meters = $Newtype<number>;expands to
export type Meters = number & { readonly [__mf_newtype_42]: true };
declare const __mf_newtype_42: unique symbol;Every expansion declares its own unique symbol, named from the expansion’s
id, so the brand needs no name and cannot collide, even for a $Newtype that
another macro emits. The declare const is type-only and erases at runtime. A base
with a top-level | or &, or a function or conditional type, is parenthesized.
$Newtype is built in and needs no import. It nests, so $Newtype<$Newtype<number>> carries both brands.
The brand does not involve the alias’s own type parameters: with type Id<T> = $Newtype<string>, Id<User> and Id<Order> are the same type.
A brand that should differ per parameter names it in the type, as in type Id<T> = string & { readonly [IdOf]: T } with a declare const IdOf: unique symbol.
Constructing Values
A newtype has no constructor of its own. Derive Decode on the alias: for a
primitive base, decode checks the typeof and runs any validators written on
the alias, which makes it the validated constructor, and is the narrowing guard.
/** @derive(Encode, Decode) */
/** @endec(nonNegative, finite) */
export type Meters = $Newtype<number>;
const parsed = Meters.decode(input); // { success, value } | { success, errors }
if (Meters.is(distance)) {
travel(distance); // narrowed to Meters
}Encode writes the bare primitive, and Default returns the primitive’s
default (or the alias’s @default(value)) cast to the newtype.
Error Handling
The macro reports an expansion error when it does not receive exactly one type argument.