Skip to content
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

Add a new type Required #15012

Closed
monolithed opened this issue Apr 4, 2017 · 35 comments · Fixed by #21919
Closed

Add a new type Required #15012

monolithed opened this issue Apr 4, 2017 · 35 comments · Fixed by #21919
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@monolithed
Copy link

monolithed commented Apr 4, 2017

It would be nice to have a new type that allows property to be required as opposited to Partial:

interface X {
    x?: string 
}

Required<X>{ }; // Property 'x is missing in type '{}'
@masaeedu
Copy link
Contributor

masaeedu commented Apr 5, 2017

This will probably depend on #13253 or #12215, whichever ends up getting implemented. You need a mapped type where you subtract undefined and null.

@monolithed
Copy link
Author

monolithed commented Apr 5, 2017

@masaeedu

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

let a: Required<{ x: number }> = { }; // Property 'x is missing in type '{}'

it seems to work well.

@masaeedu
Copy link
Contributor

masaeedu commented Apr 5, 2017

@monolithed That snippet doesn't have any optional properties in the type you're passing as a type argument to Required. This corrected snippet doesn't have the expected error:

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

let a: Required<{ x?: number }> = { };

@monolithed
Copy link
Author

monolithed commented Apr 5, 2017

@masaeedu, oh, you're right (

@aluanhaddad
Copy link
Contributor

This is what you are looking for I believe. #13224

@monolithed
Copy link
Author

monolithed commented Apr 5, 2017

This is what you are looking for I believe. #13224

So, why the following operation [P in keyof T]: T[P] is not working as expected?

@PyroVortex
Copy link

@monolithed
The operation is a homomorphic mapping, which have been configured to preserve optional and readonly modifiers. To get the desired behavior, you would need to do:

type RequiredInternal<T, K extends keyof T> = { [P in K]: T[P] }; // Use type parameter to force it not to be considered homomorphic
type Required<T> = T & RequiredInternal<T, keyof T>;

let a: Required<{ x?: number }> = { }; // => Property 'x' is missing in type '{}'

@masaeedu
Copy link
Contributor

masaeedu commented Apr 5, 2017

@PyroVortex No, I tried that yesterday (I think it might have been your comment on another issue). With tsc version 2.2.1, and the following files:

// tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "strictNullChecks": true,
        "noImplicitAny": false,
        "sourceMap": false
    }
}
// main.ts
type RequiredInternal<T, K extends keyof T> = { [P in K]: T[P] }; // Use type parameter to force it not to be considered homomorphic
type Required<T> = T & RequiredInternal<T, keyof T>;

let a: Required<{ x?: number }> = { }; // No error here
let x: number = a.x;                   // Error here because number | undefined is not assignable to number

The expected error does not appear, and a is typed as having a potentially undefined x property (number | undefined).

@PyroVortex
Copy link

@masaeedu
Huh. In the current version in the TypeScript Playground with strictNullChecks enabled, I get an error for the first assignment (missing property), but I do still see the error you are mentioning for the second.

In general, we lack an ability to remove a type from a union using pure type manipulation (we can do it with an actual value).

@masaeedu
Copy link
Contributor

masaeedu commented Apr 5, 2017

@PyroVortex How did you enable strictNullChecks in playground?

@aluanhaddad
Copy link
Contributor

image

@priithaamer
Copy link

@PyroVortex @masaeedu I am too trying to create type that maps optional fields to required fields. I get the example above to raise an error with TS 2.1.4 and in the playground (which it seems to be using). From 2.1.5 and up it does not give me an error anymore. Any idea why it is so?

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label May 24, 2017
@thehappycoder
Copy link

This feature is very handy in react stateful components when you want to specify the default state, and you want to make sure you don't miss any state.

@mikejhill
Copy link

mikejhill commented Jul 11, 2017

I'm still running into issues with this in TS 2.4.1. I've tried breaking the homomorphic relationship, as described in #13723, and I believe this does that because the fields are now required, but instead of removing all traces of optional parameters it keeps the type union with undefined. Is this intentional?

type RequiredInternal<T extends {[key: string]: any}, K extends string> = { [P in K]: T[P]};
type Required<T> = RequiredInternal<T, keyof T>;

// As expected: No compile error
const val1: Required<{x?: number}> = {x: 1};

// As expected: Compile error:
// TS2322: Type '{}' is not assignable to type 'RequiredInternal<{ x?: number | undefined; }, "x">'. Property 'x' is missing in type '{}'.
const val2: Required<{x?: number}> = {};

// Not expected?: No compile error
// Expected something like TS2322: Type '{ x: undefined; }' is not assignable to type 'RequiredInternal<{ x?: number | undefined; }, "x">'. Types of property 'x' are incompatible. Type 'undefined' is not assignable to type 'number'.
const val3: Required<{x?: number}> = {x: undefined};

@mhegazy mhegazy added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. and removed Needs Investigation This issue needs a team member to investigate its status. labels Aug 28, 2017
@MeirionHughes
Copy link

@PyroVortex I'm afraid even with strictNullCheck, your example still results in a being optional (typescript 2.4):

image

@ssonne
Copy link

ssonne commented Oct 20, 2017

Try this:

// Non-homomorphic keys
type NHKeys<T> = ({[P in keyof T]: P } & { [x:string]:never })[keyof T];

type Required<T> = {
  [K in NHKeys<T>]:T[K];
};

interface Foo {
  a?:string;
  b:string;
};

const foo:Foo = {b:'abc'};
const fooRequired:Required<Foo> = {b:'abc'}; // error

@ghost
Copy link

ghost commented Oct 20, 2017

@sandersn @weswigham Is there a better way to do this?

@weswigham
Copy link
Member

I want to say... no? @ssonne seems to have it. The core is breaking the inference of a homomorphic relationship within the mapped type; and it seems like we keep that for as long as we can (traversing aliases and simplifying unions to look for it). The intersection-index indirection seems like a sound way to go.

@MeirionHughes
Copy link

@monolithed why don't you make PR --or better yet, invite @ssonne to do it because he figured it out-- to add Required<> to the types bundled by typescript itself

@monolithed
Copy link
Author

@MeirionHughes #19398

@monolithed
Copy link
Author

The PR is closed due to:

I do not think this is a type we want to include in the standard library. if we were to address #15012, we need a type operator/ an addition to mapped types that allows such transformation.
--

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Oct 26, 2017

@ssonne I think this is a bug, and I might have a fix in mind... so... I'm sorry?

@pelotom
Copy link

pelotom commented Oct 26, 2017

@DanielRosenwasser So you’re saying this Required trick is about to be “fixed” out of existence? 😕

@mikejhill
Copy link

mikejhill commented Oct 26, 2017

I use one of the variants of the aforementioned Required type definition in my projects, and it works for its purpose (requiring all fields to be set), but when I'm using this what I usually really want is to ensure that all fields are non-null and non-undefined. Required doesn't help us there since it still allows the field to be set as undefined. This was mentioned by @masaeedu here back in April.

Thankfully, there are a few tickets out for "subtraction" types which might solve this: #4183, #12215, #15059.

@Taytay
Copy link

Taytay commented Nov 7, 2017

I just want to get clarification on this @DanielRosenwasser. Is the fact that this type works a quirk of a bug you think that @ssonne has found? So if we were to adopt this, it would stop working once the bug is fixed?

@nevir
Copy link

nevir commented Nov 14, 2017

The workaround @ssonne has doesn't work in all contexts, unfortunately, too. For example, if you enable strictNullChecks:

interface Optional {
  foo?: () => {}
}
type Strict = Required<Optional>

const impl: Strict = {
  foo() {}
};

impl.foo();
// ^ Object is possibly 'undefined'.

[Playground]

@falsandtru
Copy link
Contributor

We can make required properties, but they still have nullable values. The last piece is NonNullable type.

type Required<T> = {
  [P in Purify<keyof T>]: NonNullable<T[P]>;
};
type Purify<T extends string> = { [P in T]: T; }[T];
type NonNullable<T> = T - undefined - null;

@mpawelski
Copy link

mpawelski commented Nov 16, 2017

Well, right now we can have some workaround that allows you to define required type from partial one

type PartialPerson = {
  name? : number;
  surname? : string;
}

declare function makeRequired<T>(notRequired : Partial<T>): T;

let __requiredPersonVariable = null as any && makeRequired({} as PartialPerson);
type RequiredPerson = typeof __requiredPersonVariable;   

Then if we got #6606 we could do something like this (I guess, I'm not sure how it would work with generic type aliases if it was implemented):

type Required<T> = typeof( null as any && makeRequired({} as T))

For me It's another example how useful #6606 would be if we had it :)

@niieani
Copy link

niieani commented Nov 22, 2017

I've found out that T & {} simplifies to non-nullable type, i.e.:

type Required<T> = {
  [P in Purify<keyof T>]: NonNullable<T[P]>;
};
type Purify<T extends string> = { [P in T]: T; }[T];
type NonNullable<T> = T & {};

Thanks to @falsandtru for the Purify method.

See: Playground (remember to enable strictNullChecks).

@falsandtru
Copy link
Contributor

Great! @weswigham @sandersn @ahejlsberg Can we use type NonNullable<T> = T & {}; as a stable API?

@pelotom
Copy link

pelotom commented Nov 23, 2017

Very nice @niieani! And it makes sense: {} contains all values except undefined and null, so intersecting with it excludes those and nothing else.

@pelotom
Copy link

pelotom commented Nov 28, 2017

For those interested, I've added Required (and NonNullable) to Type Zoo.

@falsandtru
Copy link
Contributor

falsandtru commented Feb 6, 2018

Now the latest version is:

type Required<T> =
  T extends object
    ? { [P in Purify<keyof T>]: NonNullable<T[P]>; }
    : T;
type DeepRequired<T, U extends object | undefined = undefined> =
  T extends object
    ? { [P in Purify<keyof T>]: NonNullable<T[P]> extends NonNullable<U | Function | Class> ? NonNullable<T[P]> : DeepRequired<NonNullable<T[P]>, U>; }
    : T;

This type can stop the processing with the specified object types like DeepRequired<obj, Element>.

https://github.com/falsandtru/spica/blob/master/src/lib/type.ts
https://github.com/falsandtru/spica/blob/master/src/lib/type.test.ts

@ahejlsberg ahejlsberg added Fixed A PR has been merged for this issue and removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Feb 13, 2018
@mhegazy mhegazy added this to the TypeScript 2.8 milestone Feb 13, 2018
@weswigham
Copy link
Member

@falsandtru And as of today there's no need for Purify:

type Required<T> =
  T extends object
    ? { [P in keyof T]-?: NonNullable<T[P]>; }
    : T;
type DeepRequired<T, U extends object | undefined = undefined> =
  T extends object
    ? { [P in keyof T]-?: NonNullable<T[P]> extends NonNullable<U | Function | Class> ? NonNullable<T[P]> : DeepRequired<NonNullable<T[P]>, U>; }
    : T;

this should also handle assignability while things are still generic better, too; or at least that's the goal.

@falsandtru
Copy link
Contributor

Right, I'll try to use the new syntax later.

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.