Skip to content

Inlining Pick and Partial instead of using the builtins seems to result in less type instantiations #56017

@jussisaurio

Description

@jussisaurio

Acknowledgement

  • I acknowledge that issues using this template may be closed without further explanation at the maintainer's discretion.

Comment

Apologies for using the Issue: Other template, I genuinely do not know if this is a bug or not.

I'm currently investigating a TS compilation speed improvement in Zod (see relevant PR here)

It seems as though replacing usages of the builtin Pick and Partial with inlined, functionally equivalent types results in less type instantiations, which, when the delta is large, starts to affect typechecking performance.

Builtins:

// This utility is the same in both versions
type requiredKeys<T extends object> = {
  [k in keyof T]: undefined extends T[k] ? never : k;
}[keyof T];
// This uses native Pick and Partial
export type addQuestionMarks<
  T extends object,
  R extends keyof T = requiredKeys<T>
> = Pick<Required<T>, R> & Partial<T>;

Inlined:

// This utility is the same in both versions
type requiredKeys<T extends object> = {
  [k in keyof T]: undefined extends T[k] ? never : k;
}[keyof T];
// This uses inlined Pick and Partial
export type addQuestionMarks<
  T extends object,
  R extends keyof T = requiredKeys<T>
> = { [K in R]: Required<T>[K] } & {
  [K in keyof T]?: T[K] | undefined;
};

Minimal reproduction repository

Compilation output in the minimal repro repo using native Pick and Partial:

% npm run compile-native

> ts-pick-partial-comparison@1.0.0 compile-native
> tsc -p nativePickPartial/tsconfig.json --noEmit --incremental false --extendedDiagnostics

Files:                         14
Lines of Library:            7008
Lines of Definitions:           0
Lines of TypeScript:           20
Lines of JavaScript:            0
Lines of JSON:                  0
Lines of Other:                 0
Identifiers:                 7773
Symbols:                     5307
Types:                       2027
Instantiations:               566
Memory used:               28688K
Assignability cache size:     144
Identity cache size:            0
Subtype cache size:             0
Strict subtype cache size:      0
I/O Read time:              0.00s
Parse time:                 0.04s
ResolveLibrary time:        0.00s
Program time:               0.05s
Bind time:                  0.01s
Check time:                 0.07s
printTime time:             0.00s
Emit time:                  0.00s
Total time:                 0.13s

Compilation output in the minimal repro repo using inlined Pick and Partial:

% npm run compile-mapped

> ts-pick-partial-comparison@1.0.0 compile-mapped
> tsc -p manualMappedTypes/tsconfig.json --noEmit --incremental false --extendedDiagnostics

Files:                         14
Lines of Library:            7008
Lines of Definitions:           0
Lines of TypeScript:           22
Lines of JavaScript:            0
Lines of JSON:                  0
Lines of Other:                 0
Identifiers:                 7776
Symbols:                     5311
Types:                       2028
Instantiations:               543
Memory used:               28733K
Assignability cache size:     155
Identity cache size:            0
Subtype cache size:             0
Strict subtype cache size:      0
I/O Read time:              0.00s
Parse time:                 0.04s
ResolveLibrary time:        0.00s
Program time:               0.05s
Bind time:                  0.01s
Check time:                 0.06s
printTime time:             0.00s
Emit time:                  0.00s
Total time:                 0.13s

In this minimal comparison, the difference is small but it's there. In the case of bigger projects using complex types via libraries like Zod, the difference seems to balloon substantially.

The main goal of this issue, I guess, is to find out the following:

  1. Is this comparison fair, i.e. are the builtin and the inlined versions infact somehow different, and
  2. If they are equivalent, why does the inlined version result in less instantiations?

System:

% npx envinfo

  System:
    OS: macOS 13.5.2
    CPU: (10) arm64 Apple M1 Max
  Binaries:
    Node: 20.5.1 - ~/Library/Caches/fnm_multishells/79183_1696663768732/bin/node
    npm: 9.8.0 - ~/Library/Caches/fnm_multishells/79183_1696663768732/bin/npm

% npm ls typescript
└── typescript@5.2.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions