-
-
Save SamPruden/ab0c7305d241cee4c7f0452f11a4d1f1 to your computer and use it in GitHub Desktop.
| // 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>]; |
Should this be done in production? Uhh... I'm going to try it, let's see how badly it backfires. If the issue mentioned above is not a bug, then this is a little hackish, but valid TypeScript.
Even if DeepReadonly<T> does rely on a bug, I think there may be a solution with optional properties, that needs investigation.
Oh, arrays. I'm working on it.
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.
I'm a little worried that the
DeepReadonly<T>implementation may rely on a bug. Should the nestedDeepReadonly<T[K]>be valid?T[K]isn't guaranteed to matchWithTypeInfo.