-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Description
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:
- Is this comparison fair, i.e. are the builtin and the inlined versions infact somehow different, and
- 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