From 6dcf5483bb6bbb8d343db28dedb258c8da91ffac Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Wed, 20 Mar 2024 10:30:36 +0000 Subject: [PATCH] refactor(experimental): use DrainOuterGeneric helper on codec type mappings (#2322) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR aims to solve #2295 by wrapping codec type mappings with the suggested `DrainOuterGeneric`. The TypeScript diagnostics on a complex codec — using both data enums and structs — changes as follows. As you can see we get a significant reduction of instantiations. ``` Files: 204 -> 205 Lines of Library: 38131 -> 38131 Lines of Definitions: 52284 -> 52300 Lines of TypeScript: 81 -> 81 Lines of JavaScript: 0 -> 0 Lines of JSON: 0 -> 0 Lines of Other: 0 -> 0 Identifiers: 92492 -> 92518 Symbols: 111465 -> 111877 Types: 55305 -> 55390 Instantiations: 603991 -> 362113 // -40% Memory used: 187111K -> 189767K Assignability cache size: 21397 -> 21404 Identity cache size: 350 -> 350 Subtype cache size: 161 -> 161 Strict subtype cache size: 358 -> 354 I/O Read time: 0.01s -> 0.01s Parse time: 0.18s -> 0.18s ResolveModule time: 0.02s -> 0.02s ResolveTypeReference time: 0.00s -> 0.00s ResolveLibrary time: 0.01s -> 0.01s Program time: 0.23s -> 0.24s Bind time: 0.07s -> 0.07s Check time: 0.78s -> 0.72s transformTime time: 0.00s -> 0.00s commentTime time: 0.00s -> 0.00s I/O Write time: 0.00s -> 0.00s printTime time: 0.01s -> 0.01s Emit time: 0.01s -> 0.01s Total time: 1.09s -> 1.04s ``` --- .changeset/heavy-students-relax.md | 8 ++++++++ packages/codecs-data-structures/src/data-enum.ts | 10 +++++----- packages/codecs-data-structures/src/struct.ts | 10 +++++----- packages/codecs-data-structures/src/utils.ts | 12 ++++++++++++ 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 .changeset/heavy-students-relax.md diff --git a/.changeset/heavy-students-relax.md b/.changeset/heavy-students-relax.md new file mode 100644 index 000000000000..41d014438a08 --- /dev/null +++ b/.changeset/heavy-students-relax.md @@ -0,0 +1,8 @@ +--- +'@solana/codecs-data-structures': patch +--- + +Use `DrainOuterGeneric` helper on codec type mappings + +This significantly reduces the number of TypeScript instantiations on object mappings, +which increases TypeScript performance and prevents "Type instantiation is excessively deep and possibly infinite" errors. diff --git a/packages/codecs-data-structures/src/data-enum.ts b/packages/codecs-data-structures/src/data-enum.ts index 8fd87f69a1f5..6f7715019d72 100644 --- a/packages/codecs-data-structures/src/data-enum.ts +++ b/packages/codecs-data-structures/src/data-enum.ts @@ -17,7 +17,7 @@ import { SolanaError, } from '@solana/errors'; -import { getMaxSize, maxCodecSizes, sumCodecSizes } from './utils'; +import { DrainOuterGeneric, getMaxSize, maxCodecSizes, sumCodecSizes } from './utils'; /** * Defines a data enum using discriminated union types. @@ -74,21 +74,21 @@ export type DataEnumCodecConfig = readonly (readonly [string, T])[]; type ArrayIndices = Exclude['length'], T['length']> & number; -type GetEncoderTypeFromVariants>> = { +type GetEncoderTypeFromVariants>> = DrainOuterGeneric<{ [I in ArrayIndices]: (TVariants[I][1] extends Encoder ? TFrom extends object ? TFrom : object : never) & { __kind: TVariants[I][0] }; -}[ArrayIndices]; +}>[ArrayIndices]; -type GetDecoderTypeFromVariants>> = { +type GetDecoderTypeFromVariants>> = DrainOuterGeneric<{ [I in ArrayIndices]: (TVariants[I][1] extends Decoder ? TTo extends object ? TTo : object : never) & { __kind: TVariants[I][0] }; -}[ArrayIndices]; +}>[ArrayIndices]; /** * Creates a data enum encoder. diff --git a/packages/codecs-data-structures/src/struct.ts b/packages/codecs-data-structures/src/struct.ts index da347afac425..94791792d1bb 100644 --- a/packages/codecs-data-structures/src/struct.ts +++ b/packages/codecs-data-structures/src/struct.ts @@ -15,18 +15,18 @@ import { VariableSizeEncoder, } from '@solana/codecs-core'; -import { getFixedSize, getMaxSize, sumCodecSizes } from './utils'; +import { DrainOuterGeneric, getFixedSize, getMaxSize, sumCodecSizes } from './utils'; type Fields = readonly (readonly [string, T])[]; type ArrayIndices = Exclude['length'], T['length']> & number; -type GetEncoderTypeFromFields>> = { +type GetEncoderTypeFromFields>> = DrainOuterGeneric<{ [I in ArrayIndices as TFields[I][0]]: TFields[I][1] extends Encoder ? TFrom : never; -}; +}>; -type GetDecoderTypeFromFields>> = { +type GetDecoderTypeFromFields>> = DrainOuterGeneric<{ [I in ArrayIndices as TFields[I][0]]: TFields[I][1] extends Decoder ? TTo : never; -}; +}>; /** * Creates a encoder for a custom object. diff --git a/packages/codecs-data-structures/src/utils.ts b/packages/codecs-data-structures/src/utils.ts index 3a30d5ca4143..07c7e05404d1 100644 --- a/packages/codecs-data-structures/src/utils.ts +++ b/packages/codecs-data-structures/src/utils.ts @@ -1,5 +1,17 @@ import { isFixedSize } from '@solana/codecs-core'; +/** + * Functionally, this type helper is equivalent to the identity type — i.e. `type Identity = T`. + * However, wrapping generic object mappings in this type significantly reduces the number + * of instantiation expressions processed, which increases TypeScript performance and + * prevents "Type instantiation is excessively deep and possibly infinite" errors. + * + * This works because TypeScript doesn't create a new level of nesting when encountering conditional generic types. + * @see https://github.com/microsoft/TypeScript/issues/34933 + * @see https://github.com/kysely-org/kysely/pull/483 + */ +export type DrainOuterGeneric = [T] extends [unknown] ? T : never; + export function maxCodecSizes(sizes: (number | null)[]): number | null { return sizes.reduce( (all, size) => (all === null || size === null ? null : Math.max(all, size)),