Last active
August 7, 2017 19:38
-
-
Save SamPruden/ab0c7305d241cee4c7f0452f11a4d1f1 to your computer and use it in GitHub Desktop.
Messing around with a trick that allows basic type switching in TypeScript, and provides the ability to make DeepReadonly<T>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WHAT IS THIS? | |
// This is me playing around with a cool (but dirty) trick | |
// Some interfaces can be globally augmented to provide info to the type system | |
// This then allows some basic type switch and retrieval operations | |
// I needed a DeepReadonly<T> for a thing, I think I have it now | |
// Built/tested in 2.5.0-dev20170803 | |
// FUTURE PLANS | |
// - This needs lots of tidying and renaming | |
// - Do more stuff | |
// - Make better plans | |
// - Lots of refactoring | |
// - Seriously, can we at least have things named slightly consistently? | |
// String unions for typeof | |
type PrimitiveTypeString = "string" | "number" | "boolean" | "symbol"; | |
type NonPrimitiveTypeString = "object" | "function"; | |
type TypeString = PrimitiveTypeString | NonPrimitiveTypeString; | |
// There's an unnecessary level here for now, but I have plans to add some other things to !_typeinfo | |
// The leading ! prevents this from showing up in intellisense | |
// Could actually just add the property to the prototype so that this isn't a lie | |
interface WithTypeInfo<T extends TypeString = TypeString> { | |
/** | |
* Ghost property used for doing type operations, does not actually exist | |
*/ | |
readonly "!_typeInfo": { | |
readonly type: T; | |
// readonly specialType?: string; | |
} | |
} | |
// Globally extend these interfaces with typing annotations | |
interface Object extends WithTypeInfo<"object"> { } | |
interface Function extends WithTypeInfo<"function"> { } | |
interface String extends WithTypeInfo<"string"> { } | |
interface Number extends WithTypeInfo<"number"> { } | |
interface Boolean extends WithTypeInfo<"boolean"> { } | |
interface Symbol extends WithTypeInfo<"symbol"> { } | |
// Special type info provided for arrays | |
// Maybe generalise this for various things with various type parameters | |
interface Array<T> { // extends WithTypeInfo<"object"> { | |
readonly "!_typeInfo": { | |
readonly type: "object"; | |
readonly specialType: "array"; | |
readonly arrayType: T; | |
} | |
} | |
// Fetch stuff please stuff - this is a poor comment | |
type TypeInfo<T extends WithTypeInfo> = T["!_typeInfo"]; | |
type TypeOf<T extends WithTypeInfo> = TypeInfo<T>["type"]; | |
// A couple of type operation utilities | |
// Stolen, ahem, borrowed, from tycho01's typical repo | |
type Obj<T> = { [k: string]: T }; | |
type UnionHasKey<Union extends string, K extends string> = ({[S in Union]: '1' } & Obj<'0'>)[K]; | |
// Typeof operations | |
type Is<T extends WithTypeInfo, TypeUnion extends TypeString> = UnionHasKey<TypeUnion, TypeOf<T>>; | |
type IsPrimitive<T extends WithTypeInfo> = Is<T, PrimitiveTypeString>; | |
// For accepting only primative/non-primative arguments | |
type Primitive = WithTypeInfo<PrimitiveTypeString>; | |
type NonPrimitive = WithTypeInfo<NonPrimitiveTypeString>; | |
// Get the type of an array | |
type TypeOfArray<T extends Array<any>> = T["!_typeInfo"]["arrayType"]; | |
// Unsafe type, trusts that T is an array, returns never if not | |
type TypeOfArrayUnsafe<T extends WithTypeInfo> = (Obj<never> & T["!_typeInfo"])["arrayType"]; | |
type SpecialTypeOf<T> = (TypeInfo<T> & Obj<"NONE">)["specialType"]; | |
type HasSpecialType<T> = ({"NONE": "0"} & Obj<"1">)[SpecialTypeOf<T>]; | |
type IsSpecial<T, S extends string> = (Obj<"0"> & {[K in S]: "1"})[SpecialTypeOf<T>]; | |
type IsArray<T> = IsSpecial<T, "array">; | |
// I should move other things over to use this | |
type If<C extends "0" | "1", T, F> = {"0": F; "1": T;}[C]; | |
// Not currently used | |
// To do: Investigate tuples | |
type DeepReadonlyArray<T extends any[]> = ReadonlyArray<DeepReadonly<TypeOfArray<T>>>; | |
// Trusts T to be an array, treats as Array<any> if not | |
type DeepReadonlyArrayUnsafe<T> = ReadonlyArray<DeepReadonly<TypeOfArrayUnsafe<T>>>; | |
// Recursion, makes all properties deep readonly | |
type DeepReadonlyObject<T extends WithTypeInfo> = {readonly [K in keyof T]: DeepReadonly<T[K]>}; | |
// Takes any type, including primitives, and makes them deep readonly | |
type DeepReadonly<T extends WithTypeInfo> = { | |
"object": If<IsArray<T>, DeepReadonlyArrayUnsafe<T>, DeepReadonlyObject<T>>; | |
"number": T; | |
"function": T; | |
"string": T; | |
"boolean": T; | |
"symbol": T; | |
}[TypeOf<T>]; |
Arrays are proving tricky, but I'm 95% sure that is because of a bug in type declarations.
Updated DeepReadonly<T>
to handle more stuff, namely to smoothly handle arrays.
I fear DeepReadonly<T>
as implemented here may only work because of a bug like #17456. I think I may be able to fix that by removing some of the <T extends WithTypeInfo>
requirements and providing fallback values if T
doesn't extend WithTypeInfo
.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Oh, arrays. I'm working on it.