-
Notifications
You must be signed in to change notification settings - Fork 9.5k
misc: tighten RecursivePartial type #11175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,30 +7,6 @@ | |
import _Crdp from 'devtools-protocol/types/protocol'; | ||
import _CrdpMappings from 'devtools-protocol/types/protocol-mapping' | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💀 |
||
// Convert unions (T1 | T2 | T3) into tuples ([T1, T2, T3]). | ||
// https://stackoverflow.com/a/52933137/2788187 https://stackoverflow.com/a/50375286 | ||
type UnionToIntersection<U> = | ||
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never | ||
|
||
type UnionToFunctions<U> = | ||
U extends unknown ? (k: U) => void : never; | ||
|
||
type IntersectionOfFunctionsToType<F> = | ||
F extends { (a: infer A): void; (b: infer B): void; (c: infer C): void; (d: infer D): void; } ? [A, B, C, D] : | ||
F extends { (a: infer A): void; (b: infer B): void; (c: infer C): void; } ? [A, B, C] : | ||
F extends { (a: infer A): void; (b: infer B): void; } ? [A, B] : | ||
F extends { (a: infer A): void } ? [A] : | ||
never; | ||
|
||
type SplitType<T> = | ||
IntersectionOfFunctionsToType<UnionToIntersection<UnionToFunctions<T>>>; | ||
|
||
// (T1 | T2 | T3) -> [RecursivePartial(T1), RecursivePartial(T2), RecursivePartial(T3)] | ||
type RecursivePartialUnion<T, S=SplitType<T>> = {[P in keyof S]: RecursivePartial<S[P]>}; | ||
|
||
// Return length of a tuple. | ||
type GetLength<T extends any[]> = T extends { length: infer L } ? L : never; | ||
|
||
declare global { | ||
// Augment Intl to include | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/getCanonicalLocales | ||
|
@@ -51,30 +27,13 @@ declare global { | |
}; | ||
|
||
/** Make optional all properties on T and any properties on object properties of T. */ | ||
type RecursivePartial<T> = { | ||
[P in keyof T]+?: | ||
// RE: First two conditions. | ||
// If type is a union, map each individual component and transform the resultant tuple back into a union. | ||
// Only up to 4 components of a union is supported (all but the last few are dropped). For more, modify the second condition | ||
// and `IntersectionOfFunctionsToType`. | ||
// Ex: `{passes: PassJson[] | null}` - T[P] doesn't exactly match the array-recursing condition, so without these first couple | ||
// conditions, it would fall through to the last condition (would just return T[P]). | ||
|
||
// RE: First condition. | ||
// Guard against large string unions, which would be unreasonable to support (much more than 4 components is common). | ||
|
||
SplitType<T[P]> extends string[] ? T[P] : | ||
GetLength<SplitType<T[P]>> extends 2|3|4 ? RecursivePartialUnion<T[P]>[number] : | ||
|
||
// Recurse into arrays. | ||
T[P] extends (infer U)[] ? RecursivePartial<U>[] : | ||
|
||
// Recurse into objects. | ||
T[P] extends (object|undefined) ? RecursivePartial<T[P]> : | ||
|
||
// Strings, numbers, etc. (terminal types) end here. | ||
T[P]; | ||
}; | ||
type RecursivePartial<T> = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey, now here's a type I can understand 😆 I'm curious how this solves the original
Where does that happen? Does typescript do this for us magically somehow? AFAICT this just applies to arrays and straight up objects and everything else just returns
Hallelujah 🎉 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Basically, yes, but in a mostly good way :) I should probably have explained why that works now, though. They define it kind of poorly, but as long as the type parameter is used directly as part of the check, the conditional type will distribute across unions.
So (if working correctly):
In this case the previous code was checking the conditionals against There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The other type of conditional that breaks distribution is if the type parameter isn't the thing being checked against. The So if we do end up using something like that, it should only be called by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gotcha, yeah that makes sense 👍 I guess I never really questioned |
||
// Recurse into arrays and tuples: elements aren't (newly) optional, but any properties they have are. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this does treat tuples the same as arrays, where sometimes you really might always want an array of a certain length (so you use a tuple for the type) and it's ok if it's type RecursivePartialArrayOrTuple<T> = T extends (infer U)[] ?
number extends T['length'] ?
// Array or unbounded-length tuple.
RecursivePartial<U>[] :
// Bounded-length tuple.
{[P in keyof T]?: RecursivePartial<T[P]>} :
never; That does still treat unbounded-length tuples (e.g. |
||
T extends (infer U)[] ? RecursivePartial<U>[] : | ||
// Recurse into objects: properties and any of their properties are optional. | ||
T extends object ? {[P in keyof T]?: RecursivePartial<T[P]>} : | ||
// Strings, numbers, etc. (terminal types) end here. | ||
T; | ||
|
||
/** Recursively makes all properties of T read-only. */ | ||
export type Immutable<T> = | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't able to make
RecursivePartial<LH.Config.Json>
work, butLH.Config.Json
is so close to fully optional that the only thing needed to be used directly was sprinkling in a fewpassName
s in the test configs below. Since it's such a small amount of extra setup and we did makepassName
required on purpose, it seemed like a good tradeoff to make.