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 '{}'
This will probably depend on #13253 or #12215, whichever ends up getting implemented. You need a mapped type where you subtract undefined
and null
.
@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.
@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 }> = { };
@masaeedu, oh, you're right (
This is what you are looking for I believe. https://github.com/Microsoft/TypeScript/issues/13224
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?
@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 '{}'
@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
).
@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).
@PyroVortex How did you enable strictNullChecks
in playground?
@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?
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.
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};
@PyroVortex I'm afraid even with strictNullCheck, your example still results in a being optional (typescript 2.4):
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
@sandersn @weswigham Is there a better way to do this?
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.
@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
@MeirionHughes https://github.com/Microsoft/TypeScript/pull/19398
The PR is closed due to:
@ssonne I think this is a bug, and I might have a fix in mind... so... I'm sorry?
@DanielRosenwasser So youâre saying this Required
trick is about to be âfixedâ out of existence? đ
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.
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?
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](https://www.typescriptlang.org/play/index.html#src=type%20NHKeys%3CT%3E%20%3D%20(%7B%5BP%20in%20keyof%20T%5D%3A%20P%20%7D%20%26%20%7B%20%5Bx%3Astring%5D%3Anever%20%7D)%5Bkeyof%20T%5D%3B%0D%0A%0D%0Atype%20Required%3CT%3E%20%3D%20%7B%0D%0A%20%20%5BK%20in%20NHKeys%3CT%3E%5D%3AT%5BK%5D%3B%0D%0A%7D%3B%0D%0A%0D%0Ainterface%20Optional%20%7B%0D%0A%20%20foo%3F%3A%20()%20%3D%3E%20void%0D%0A%7D%0D%0Atype%20Strict%20%3D%20Required%3COptional%3E%0D%0A%0D%0Aconst%20impl%3A%20Strict%20%3D%20%7B%0D%0A%20%20foo()%20%7B%7D%0D%0A%7D%3B%0D%0A%0D%0Aimpl.foo()%3B%0D%0A%2F%2F%20%5E%20Object%20is%20possibly%20'undefined'.%0D%0A)]
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;
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 :)
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).
Great! @weswigham @sandersn @ahejlsberg Can we use type NonNullable<T> = T & {};
as a stable API?
Very nice @niieani! And it makes sense: {}
contains all values except undefined
and null
, so intersecting with it excludes those and nothing else.
For those interested, I've added Required
(and NonNullable
) to Type Zoo.
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
@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.
Right, I'll try to use the new syntax later.
Most helpful comment
I've found out that
T & {}
simplifies to non-nullable type, i.e.:Thanks to @falsandtru for the Purify method.
See: Playground (remember to enable strictNullChecks).