It technically possible to implement units using generic and literal types in TypeScript:

// union of all possible unit types type UnitType = 'cm' | 'm'; interface UnitConversion<From extends UnitType, To extends UnitType> { from: From; to: To; convert(value: UnitValue<From>): UnitValue<To>; } function conversion<From extends UnitType, To extends UnitType>( from: From, to: To, convert: (value: UnitValue<From>) => UnitValue<To> ): UnitConversion<From, To> { return { from, to, convert }; } function identity<T extends UnitType>(t: T): UnitConversion<T, T> { return { from: t, to: t, convert: v => v }; } // conversion table for each pair of unit types const IMPLICIT_CONVERSIONS = { 'cm': { 'cm': identity('cm'), 'm': conversion('cm', 'm', v => new UnitValue(v.value * 0.1, 'm')), }, 'm': { 'cm': conversion('m', 'm', v => new UnitValue(v.value * 10, 'cm')), 'm': identity('m'), }, }; type ImplicitConversions< Left extends UnitType, Right extends UnitType > = (typeof IMPLICIT_CONVERSIONS)[Left][Right]['to']; function convert(conversion: UnitConversion<any, any>, value: UnitValue<any>) { return value.type === conversion.to ? value : conversion.convert(value); } type UnitPair<T extends UnitType> = { left: UnitValue<T>; right: UnitValue<T>; }; function convertToCommonType<Left extends UnitType, Right extends UnitType>( left: UnitValue<Left>, right: UnitValue<Right> ): UnitPair<ImplicitConversions<Left, Right>> { const conversion = IMPLICIT_CONVERSIONS[left.type][right.type]; return { left: convert(conversion, left), right: convert(conversion, right) }; } class UnitValue<Type extends UnitType> { constructor( readonly value: number, readonly type: Type, ) { } /** Type-safe unit addition */ add<T extends UnitType>(value: UnitValue<T>): UnitValue<ImplicitConversions<Type, T>> { const { left, right } = convertToCommonType(this, value); return new UnitValue(left.value + right.value, left.type); } }

Then use it like this:

const common = convertToCommonType( new UnitValue(3, 'cm'), new UnitValue(10, 'm') ); // => result type: UnitValue<'m'> const z = new UnitValue(4, 'cm').add(new UnitValue(5, 'm')); // => result type: UnitValue<'m'>

However, it could be argued that this introduces too much complexity.