Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript strict alias checking

Is there a way to enforce typescript to even more strict typechecking up to per alias checks?

What I want to achieve is to define types, such as:

type kilograms = number;
type kilometers = number;
type kilogramsPerKilometer = number;

And to be sure that I don't put wrongly typed value into a variable such as:

let x: kilograms = 123;
let y: kilometers = 256;
let z: kilogramsPerKilometer = x / y; // Will popup an error here saying that types are incompatible

In which case it would require explicit type cast:

let x: kilograms = 123;
let y: kilometers = 256;
let z: kilogramsPerKilometer = <number>x / <number>y; // Will downcast `kilograms` and `kilometers` types to `number` and then up-cast types to `kilogramsPerKilometer`
like image 428
Lu4 Avatar asked Jan 22 '17 11:01

Lu4


3 Answers

There is no nominal typing in TS (yet) https://github.com/Microsoft/TypeScript/issues/202

This is the admitted solution (using discriminating unions):

interface kilograms {
  kind: "kilograms";
  value: number;
}

interface kilometers {
  kind: "kilometers";
  value: number;
}

function kilosPerKiloms(x: kilograms, y: kilometers): kilogramsPerKilometer {
  return x.value / y.value;
}

const x = { kind: "kilograms", value: 123 };
const y = { kind: "kilometers", value: 256 };
const z = kilosPerKiloms(x, y);
// const z = kilosPerKiloms(y, x); // => error

More informations there:

https://www.typescriptlang.org/docs/handbook/advanced-types.html

https://basarat.gitbooks.io/typescript/content/docs/types/discriminated-unions.html

like image 169
Benoit B. Avatar answered Oct 23 '22 14:10

Benoit B.


Somehow I lost track of this question while actually finding an interesting solution. It's prettier, doesn't affect interface and doesn't pollute the IDE's list of suggestions.

export type Miles = number & { readonly '': unique symbol };
export type Kilograms = number & { readonly '': unique symbol };
export type MilesPerKilogram = number & { readonly '': unique symbol };

var miles: Miles = 3; // error
var kilos: Kilograms = 3; // error
var milesPerKilos: MilesPerKilogram = x / y; // error

var x: Miles = 3 as Miles; // ok
var y: Kilograms = 3 as Kilograms; // ok
var u: MilesPerKilogram = x / y as MilesPerKilogram; // ok

miles = kilos; // error, yey!
kilos = miles; // error, yey!

Works on any types


Edit

Be careful as you are not allowed to extract { readonly '': unique symbol } part into separate type as it would share unique symbol across the types and make them share "nominality" which would beat the purpose of a nominal type:

export type Nominal<T> = T & { readonly '': unique symbol };

export type Miles = Nominal<number>;
export type Kilograms = Nominal<number>;
export type MilesPerKilogram = Nominal<number>;

var miles: Miles = 3; // error
var kilos: Kilograms = 3; // error
var milesPerKilos: MilesPerKilogram = x / y; // error

var x: Miles = 3 as Miles; // ok
var y: Kilograms = 3 as Kilograms; // ok
var u: MilesPerKilogram = x / y as MilesPerKilogram; // ok

miles = kilos; // ok, but it should be error! <================= This is due to declaration of Nominal<T>

Consider also https://basarat.gitbook.io/typescript/main-1/nominaltyping for more info

like image 45
Lu4 Avatar answered Oct 23 '22 14:10

Lu4


You are looking for nominal typing. Support is planned for future TypeScript versions (see Roadmap). For now, you have to use this: https://basarat.gitbooks.io/typescript/docs/tips/nominalTyping.html

like image 2
nikeee Avatar answered Oct 23 '22 13:10

nikeee