Skip to content

Instantly share code, notes, and snippets.

@mikearnaldi
Created August 13, 2020 10:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikearnaldi/4f2c69a53a00c67edf842a4d4ed9cfbb to your computer and use it in GitHub Desktop.
Save mikearnaldi/4f2c69a53a00c67edf842a4d4ed9cfbb to your computer and use it in GitHub Desktop.
/**
* Base types
*/
export type X = string;
export type Y = "nominal";
export type Z = string | symbol;
export type Extends<A, B> = [A] extends [B]
? "sub-type"
: [B] extends [A]
? "super-type"
: "unrelated";
export interface Covariant<T> {
covariant: () => T;
}
export interface Contravariant<T> {
contravariant: (_: T) => void;
}
/**
* Liskov coehrent type refinement
*/
// coherent because Y extends X
export type YX = Extends<Y, X>; // sub-type
export interface CovariantExtended extends Covariant<X> {
covariant: () => Y;
}
// coherent because X extends Z
export type ZX = Extends<Z, X>; // super-type
export interface ContravariantExtended extends Contravariant<X> {
contravariant: (_: Z) => void;
}
/**
* Liskov non-coehrent type refinement
*/
// @ts-expect-error
export interface ContravariantInvalidExtension extends Contravariant<X> {
contravariant: (_: Y) => void;
}
// @ts-expect-error
export interface CovariantInvalidExtension extends Covariant<X> {
covariant: () => Z;
}
/**
* Invariant = covariant + contravariant
*
* type { foo: string } =~ type { getFoo: () => string, setFoo: () => void }
*
* Where =~ represents an isomorphism of types
*
* getFoo represents the covariant side
* setFoo represents the contravariant side
*/
export interface Foo {
foo: string;
}
// @ts-expect-error
export interface FooA extends Foo {
foo: Z; // breaks the covariant side
}
export interface FooB extends Foo {
foo: Y; // TS is unsound here because Y breaks the contravariant side (we are allowing mutability)
}
export declare const fooB: FooB;
// @ts-expect-error
fooB.foo = "correctly breaks"; // sure, I cannot assign a string to "foo"
// set foo in a generic Foo
export function setFoo(foo: Foo) {
foo.foo = "I am setting a string";
}
// sure, FooB extends Foo.....
setFoo(fooB);
// @ts-expect-error
fooB.foo === "I am setting a string"; // runtime true, type level invalid
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment