You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Mapped types currently support adding a readonly or ? modifier to a mapped property, but they do not provide support the ability to remove modifiers. This matters in homomorphic mapped types (see #12563 and #12826) which by default preserve the modifiers of the underlying type.
With this PR we provide the ability for a mapped type to either add or remove a particular modifier. Specifically, a readonly or ? property modifier in a mapped type can now be prefixed with either + or - to indicate that the modifier should be added or removed.
Using this ability, the PR defines a new Required<T> type in lib.d.ts. This type strips ? modifiers from all properties of T, thus making all properties required:
typeRequired<T>={[PinkeyofT]-?: T[P]};
Some examples of + and - on mapped type modifiers:
typeMutableRequired<T>={-readonly[PinkeyofT]-?: T[P]};// Remove readonly and ?typeReadonlyPartial<T>={+readonly[PinkeyofT]+?: T[P]};// Add readonly and ?
A modifier with no + or - prefix is the same as a modifier with a + prefix. So, the ReadonlyPartial<T> type above corresponds to
typeReadonlyPartial<T>={readonly[PinkeyofT]?: T[P]};// Add readonly and ?
In --strictNullChecks mode, when a homomorphic mapped type removes a ? modifier from a property in the underlying type it also removes undefined from the type of that property:
typeFoo={a?: string};// Same as { a?: string | undefined }typeBar=Required<Foo>;// Same as { a: string }
typeOptionalPropNames<T>={[PinkeyofT]: undefinedextendsT[P] ? P : never}[keyofT];typeRequiredPropNames<T>={[PinkeyofT]: undefinedextendsT[P] ? never : P}[keyofT];typeOptionalProps<T>={[PinOptionalPropNames<T>]: T[P]};typeRequiredProps<T>={[PinRequiredPropNames<T>]: T[P]};typeFoo={a: string;b: number;c?: string;d?: number;}typeT1=RequiredProps<Foo>;// { a: string, b: number }typeT2=OptionalProps<Foo>;// { c?: string | undefined, d?: number | undefined }
Strictly speaking this doesn't filter on optional vs. required, but rather whether undefined is assignable to the property or not. But given that required properties rarely if ever have undefined as a permitted value, it's largely the same.
@Aleksey-Bykov the syntax makes me uncomfortable too. TypeScript is steadily building up really useful full compile-time programming capabilities, but some of the syntax seems very specific to a few cases (e.g. this PR). Having a few highly orthogonal compile-time programming constructs that also covered these special cases would be ideal. But I'm sure that's much easier said than done.
on a constructive note, at some point when there are over 10 different modifiers at play we gonna need some meta syntax to control them, something like this, forgive my french:
{
[{ // <--- sort of attribute syntax of C#?
amIReadonly: true || shouldIBeSomethingElse,
canBeUndefined: canBePossiblyUndefined && thereIsNoHope
}]
value: number
}
These look good to me, and I'm actually pretty OK with the syntax. If something better were suggested, sure, but I'd be comfortable/happy with using this one.
As for @Aleksey-Bykov suggestions, compile-time, type-domain functions in general would be a really useful thing to have. Conditional types get us a big step closer to that, but we still aren't there. This might be a nice place to start.
I think we're making big strides forward. With the pattern matching and recursion, I got a bunch of types to work that just hadn't been possible before (e.g. setting/removing readonly/? recursively in nested structures, flattening promises/arrays).
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
None yet
7 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mapped types currently support adding a
readonlyor?modifier to a mapped property, but they do not provide support the ability to remove modifiers. This matters in homomorphic mapped types (see #12563 and #12826) which by default preserve the modifiers of the underlying type.With this PR we provide the ability for a mapped type to either add or remove a particular modifier. Specifically, a
readonlyor?property modifier in a mapped type can now be prefixed with either+or-to indicate that the modifier should be added or removed.Using this ability, the PR defines a new
Required<T>type inlib.d.ts. This type strips?modifiers from all properties ofT, thus making all properties required:Some examples of
+and-on mapped type modifiers:A modifier with no
+or-prefix is the same as a modifier with a+prefix. So, theReadonlyPartial<T>type above corresponds toIn
--strictNullChecksmode, when a homomorphic mapped type removes a?modifier from a property in the underlying type it also removesundefinedfrom the type of that property:Fixes #15012.