Conversation

ahejlsberg

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:

type Required<T> = { [P in keyof T]-?: T[P] };

Some examples of + and - on mapped type modifiers:

type MutableRequired<T> = { -readonly [P in keyof T]-?: T[P] };  // Remove readonly and ?
type ReadonlyPartial<T> = { +readonly [P in keyof T]+?: 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

type ReadonlyPartial<T> = { readonly [P in keyof T]?: 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:

type Foo = { a?: string };  // Same as { a?: string | undefined }
type Bar = Required<Foo>;  // Same as { a: string }

Fixes #15012.

@mhegazy

We should talk about this in the next design meeting though.

@ahejlsbergahejlsberg merged commit f8a378a into master Feb 13, 2018
@ahejlsbergahejlsberg deleted the mappedTypeModifiers branch February 13, 2018 19:21
@yortus

Is there any way filter out just the required properties or just the optional properties?

I've been trying to make a type mapping that does this with no luck so far.

@ahejlsberg

@yortus Something like this:

type OptionalPropNames<T> = { [P in keyof T]: undefined extends T[P] ? P : never }[keyof T];
type RequiredPropNames<T> = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T];

type OptionalProps<T> = { [P in OptionalPropNames<T>]: T[P] };
type RequiredProps<T> = { [P in RequiredPropNames<T>]: T[P] };

type Foo = {
    a: string;
    b: number;
    c?: string;
    d?: number;
}

type T1 = RequiredProps<Foo>;  // { a: string, b: number }
type T2 = 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.

@zpdDG4gta8XKpMCd

i salute the idea, but somehow this syntax doesn't seem scalable

@yortus

@ahejlsberg thanks, that's just what I need.

@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.

@zpdDG4gta8XKpMCd

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
}

or something like Object.defineProperty

@zpdDG4gta8XKpMCd

to extend this idea to mapped or nested types by making a set modifiers a function of the previous/higher set of modifiers

{
     [before => ({
           isReadonly: !before.isReadonly,
           canBeUndefined: true
     })]
     value: number
}

@krryan

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.

@KiaraGrouwstra

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 free to subscribe to this conversation on . Already have an account? Sign in.
None yet
None yet

Successfully merging this pull request may close these issues.

Add a new type Required