With ExactOptionalPropertyTypes Enabled, Returning An Object With Missing Falsy Properties Is Incorrectly Allowed
🔎 Search Terms
- "exactoptionalpropertytypes union"
- "function call union assignability"
- "discriminated union assignment allowed incorrectly"
- "discriminated union assignable missing property"
🕗 Version & Regression Information
This issue was first introduced between versions 4.3.5 and 4.4.4 of TypeScript. Unfortunately, it still exists in the nightly version 5.9.0.
⏯ Playground Link
You can try the code in the TypeScript Playground by clicking on this link: https://www.typescriptlang.org/play/?exactOptionalPropertyTypes=true&ts=5.9.0-dev.20250508#code/KYDwDg9gTgLgBDAnmYcCqcC8cCQBYAKBwB84BvJFALjgHIBBWgbjgDcBDAGwFdgaA7bp04BfQiXKU+dAELM2XXjQDOMKAEt+AcxFNChAGbd+AYxjqI-OCa6cAPABUAfAAoDNFwEosTuA880DuRQwDDcUFYGXmIEhKCQsHBGpuaWScZmFvwAwrZeNBhk4gD0xXDKABYQQgAmcMBQUNBwAEbc8DUQwMpw-BAw4iFhEdZ5Xj7k4jjqBnAuALLsMBUAdFDs-J0Atl7eRUQ4OKW9-XDsysrqWvzsLZyoMBDoc1vqF5pacAAGHDzSgsIvp4pjghuErBRkNIGLRdFMYlMwSNIdRZLQADQKP40WgwbowWF6IgiTxEhEEeLQeDJTJpGrqEJmABKoXB+We+2mswWS1W602EB2nj2U2ODihdDICChNAARPRZSwRLQ4G8TvBzpdrrd7ggnlI6GhaCtCHBcEcymaAApNFCwRB0X68FVq17vbSqqwG2hSqRyhVKlVteAhACO3AZwDqmmlKElsek8sVWKUvSEnEDJoOSIhfrojDhxP0RBzkhlaMxTuheNUhMIMSAA
💻 Code
export type U =
| {type: 'A'; value: null}
| {type: 'B'; value: string};
function call<T>(f: () => T): T {return f()}
export function functionCall(): U {
// should error but does not
return call(() => {
if (Math.random()) {
// not assignable to U (missing `value: null`)
return {type: 'A'};
}
return {type: 'B', value: 'test'};
});
}
export function directReturn(): U {
if (Math.random()) {
// Type '{ type: "A"; }' is not assignable to type 'U'.
// Property 'value' is missing in type '{ type: "A"; }' but required in type '{ type: "A"; value: null; }'.
return {type: 'A'};
}
return {type: 'B', value: 'test'};
}
🙁 Actual behavior
The directReturn
function throws a type error because the object {type: 'A'}
is not assignable to the type U
, which requires a value
property. However, the functionCall
function does not throw a type error, even though it returns an object with a missing value
property.
🙂 Expected behavior
We expect both directReturn
and functionCall
to throw a type error because the object {type: 'A'}
is not assignable to the type U
, which requires a value
property.
Additional information about the issue
It's worth noting that this issue does not occur when exactOptionalPropertyTypes
is disabled. This suggests that the issue is related to the way TypeScript handles optional properties when exactOptionalPropertyTypes
is enabled.
Understanding the issue
The issue arises from the way TypeScript handles optional properties when exactOptionalPropertyTypes
is enabled. When this flag is enabled, TypeScript checks if the properties of an object are exactly the same as the properties of the type. If they are not, TypeScript throws a type error.
In the case of the functionCall
function, the object returned by the callback function has a missing value
property, which is not present in the type U
. However, TypeScript does not throw a type error because it is able to infer the type of the object from the context.
Why does this happen?
This issue occurs because TypeScript is able to infer the type of the object from the context. When exactOptionalPropertyTypes
is enabled, TypeScript checks if the properties of an object are exactly the same as the properties of the type. However, in this case, the object has a missing value
property, which is not present in the type U
. TypeScript is able to infer the type of the object from the context, which is why it does not throw a type error.
How to fix this issue
To fix this issue, we need to disable the exactOptionalPropertyTypes
flag or use a different approach to handle optional properties. One possible solution is to use the ?
operator to make the value
property optional in the type U
.
Conclusion
In conclusion, the issue of returning an object with missing falsy properties is incorrectly allowed when exactOptionalPropertyTypes
is enabled. This issue occurs because TypeScript is able to infer the type of the object from the context, which is why it does not throw a type error. To fix this issue, we need to disable the exactOptionalPropertyTypes
flag or use a different approach to handle optional properties.
Additional information
It's worth noting that this issue is not specific to the functionCall
function and can occur in other situations as well. Additionally, this issue is not related to the directReturn
function, which throws a type error as expected.
Related issues
This issue is related to other issues that involve the handling of optional properties in TypeScript. Some of these issues include:
- Discriminated union assignment allowed incorrectly
- Function call union assignability
- Discrim union assignable missing property
References
🔎 Search Terms
- "exactoptionalpropertytypes union"
- "function call union assignability"
- "discriminated union assignment allowed incorrectly"
- "discriminated union assignable missing property"
🕗 Version & Regression Information
This issue was first introduced between versions 4.3.5 and 4.4.4 of TypeScript. Unfortunately, it still exists in the nightly version 5.9.0.
⏯ Playground Link
You can try the code in the TypeScript Playground by clicking on this link: https://www.typescriptlang.org/play/?exactOptionalPropertyTypes=true&ts=5.9.0-dev.20250508#code/KYDwDg9gTgLgBDAnmYcCqcC8cCQBYAKBwB84BvJFALjgHIBBWgbjgDcBDAGwFdgaA7bp04BfQiXKU+dAELM2XXjQDOMKAEt+AcxFNChAGbd+AYxjqI-OCa6cAPABUAfAAoDNFwEosTuA880DuRQwDDcUFYGXmIEhKCQsHBGpuaWScZmFvwAwrZeNBhk4gD0xXDKABYQQgAmcMBQUNBwAEbc8DUQwMpw-BAw4iFhEdZ5Xj7k4jjqBnAuALLsMBUAdFDs-J0Atl7eRUQ4OKW9-XDsysrqWvzsLZyoMBDoc1vqF5pacAAGHDzSgsIvp4pjghuErBRkNIGLRdFMYlMwSNIdRZLQADQKP40WgwbowWF6IgiTxEhEEeLQeDJTJpGrqEJmABKoXB+We+2mswWS1W602EB2nj2U2ODihdDICChNAARPRZSwRLQ4G8TvBzpdrrd7ggnlI6GhaCtCHBcEcymaAApNFCwRB0X68FVq17vbSqqwG2hSqRyhVKlVteAhACO3AZwDqmmlKElsek8sVWKUvSEnEDJoOSIhfrojDhxP0RBzkhlaMxTuheNUhMIMSAA
💻 Code
export type U =
| {type: 'A'; value: null}
| {type: 'B'; value: string};
function call<T>(f: () => T): T {return f()}
export function functionCall(): U {
// should error but does not
return call(() => {
if (Math.random()) {
// not assignable to U (missing `value: null`)
return {type: 'A'};
}
return {type: 'B', value: 'test'};
});
}
export function directReturn(): U {
if (Math.random()) {
// Type '{ type: "A"; }' is not assignable to type 'U'.
// Property 'value' is missing in type '{ type: "A"; }' but required in type '{ type: "A"; value: null; }'.
return {type: 'A }
return {type: 'B', value: 'test'};
}
Q&A
Q: What is the issue with exactOptionalPropertyTypes enabled?
A: When exactOptionalPropertyTypes
is enabled, TypeScript checks if the properties of an object are exactly the same as the properties of the type. If they are not, TypeScript throws a type error. However, in this case, the object returned by the functionCall
function has a missing value
property, which is not present in the type U
. TypeScript is able to infer the type of the object from the context, which is why it does not throw a type error.
Q: Why does this issue occur?
A: This issue occurs because TypeScript is able to infer the type of the object from the context. When exactOptionalPropertyTypes
is enabled, TypeScript checks if the properties of an object are exactly the same as the properties of the type. However, in this case, the object has a missing value
property, which is not present in the type U
. TypeScript is able to infer the type of the object from the context, which is why it does not throw a type error.
Q: How to fix this issue?
A: To fix this issue, we need to disable the exactOptionalPropertyTypes
flag or use a different approach to handle optional properties. One possible solution is to use the ?
operator to make the value
property optional in the type U
.
Q: Is this issue specific to the functionCall function?
A: No, this issue is not specific to the functionCall
function. It can occur in other situations as well.
Q: Is this issue related to other issues?
A: Yes, this issue is related to other issues that involve the handling of optional properties in TypeScript. Some of these issues include:
- Discriminated union assignment allowed incorrectly
- Function call union assignability
- Discrim union assignable missing property
Q: What is the expected behavior?
A: We expect both directReturn
and functionCall
to throw a type error because the object {type: 'A'}
is not assignable to the type U
, which requires a value
property.
Q: What is the actual behavior?
A: The directReturn
function throws a type error as expected, but the functionCall
function does not throw a type error, even though it returns an object with a missing value
property.
Q: How to reproduce this issue?
A: You can reproduce this issue by enabling the exactOptionalPropertyTypes
flag and running the code in the TypeScript Playground.
Q: What is the solution to this issue?
A: To fix this issue, we need to disable the exactOptionalPropertyTypes
flag or use a different approach to handle optional properties. One possible solution is to use the ?
operator to make the value
property optional in the type U
.