With ExactOptionalPropertyTypes Enabled, Returning An Object With Missing Falsy Properties Is Incorrectly Allowed

by ADMIN 114 views

🔎 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:

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:

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.