Skip to content

exactOptionalPropertyTypes: strict type equality is too lenientΒ #61547

Closed

Description

πŸ”Ž Search Terms

"exactOptionalPropertyTypes", "strict type equality"

πŸ•— Version & Regression Information

  • Tested in TypeScript 5.8.2

⏯ Playground Link

https://www.typescriptlang.org/play/?exactOptionalPropertyTypes=true#code/PTAEFMA8EMGMBcDyAHeBLA9gO2gGwAoBOGy4h8AngCoWkDOAXAFBOWmhUCMoAvKAN5NQoZMWQB+BqDrxCaLAHMANEwC+rWuA4AmXgKEixk0AFcsAE3AAzeeHOgAPtNnzlajewD6egNoGQoIAy5Bzc0BY6oNCEWrDYdGiW0fYycgi4FBAAjiZ4BgCCdHRk8AA8AKLZeCUGwlxKOgYAfI0qwgFckeFUulFaWBjwoLFY8Yl2oLgYGEXpWTm4+YXFJQByAyUAMlNFFfPVwrWc9d1Nza2g7bpodKD9g9CFaAo4AEa4WvAYIYtF5KvrBXiz2gb3A+wOdQawmaLX8YA610ij2BoNAnyhoEBy0BT1e73BtW0x04pxUAF0ANwsNhaLF-TxUCCQeDgCw3WQmcCNPQANwwCSpAUAvBuAI-2OKkANZor4uBQKMjOVKDGlzPBoSgAOlAAGUMCZCLBwFIABbweDIRggBTq40mF4a2IAW2AAFk0LBiHQMFZ4MAaKRtR60KhgNc6Jy6MBtAB2AAM2gALABiMOcp2O1nwAC0Ce0nAArNoAJwANnzsY8Wl2VQAGvUAJrcngGAAUJSojRbAEpeNzGVAWWzQDXQOJQNwpNoewPWeY6K3252ezw+0zB3PQPXR+PQJOe2OOVopFY8EUDFSVVtpuBq7gSnXN02DD4a2S17Obj562SDGOW1+3xnIcXzfA9CE5XdQBPXAii7Axj1PcBz0rUA1lKAAhd8hxeKZ3jCJ82jAAADW8iNAF5wFgaATCKUAAHcwmVL5oD5BJUPWMIKG5CjXFAIjoKKMiMEIPjDyIgxbxKdD6kPbkgI3Q8am3ASkIOSCW0k6SoMQuTmQ-NFwK0MCIKkLBwB5Mg4OEC9NExZE8TBKgonleB6l1fVDSbUAfHcg1wEAvTgKcwgXNAgyTO0mDwCpIA

πŸ’» Code

// exactOptionalPropertyTypes:

type T1 = {
  prop?: string,
}
type T2 = {
  prop?: undefined | string,
}
type _ = [
  // ❌ T1 and T2 are considered strictly equal
  Assert<Equal<
    T1, T2
  >>,
  // T2 is not assignable to T1
  Assert<Not<Assignable<
    T1, T2
  >>>,
  // T1 is assignable to T2
  Assert<Assignable<
    T2, T1
  >>,
  // T1 and T2 are not considered loosely equal
  Assert<Not<LooseEqual<
    T1, T2
  >>>,
];

type Assert<_T extends true> = void;
// 🟒 Trick to trigger strict type equality. Source: https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650
type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false
  ;
type LooseEqual<X, Y> =
  [X] extends [Y]
  ? ([Y] extends [X] ? true : false)
  : false
  ;
type Not<B extends boolean> =
  // `Equal` because want to avoid Not<any> being `false` or `true`
  Equal<B, true> extends true
    ? false
    : (Equal<B, false> extends true ? true : never)
  ;
type Assignable<Target, Source> = [Source] extends [Target] ? true : false;

πŸ™ Actual behavior

T1 and T2 are considered to be strictly equal – even though T2 is not assignable to T1.

πŸ™‚ Expected behavior

T1 and T2 should not be considered strictly equal.

Additional information about the issue

Activity

  1. louwers commented on Apr 7, 2025

    @louwers

    I think this is not so much a bug with TypeScript, but just a bug with the Equal helper function that was shared in 2018.

  2. rauschma commented on Apr 7, 2025

    @rauschma
    Author

    So maybe this is more of a feature suggestion than a bug report? People need this functionality:

  3. louwers commented on Apr 7, 2025

    @louwers

    The issue you linked is a feature suggestion for this. #27024

    Someone opened a new issue #48100

  4. HansBrende commented on May 9, 2025

    @HansBrende
    Contributor

    I think this is not so much a bug with TypeScript

    It does indicate a bug in TypeScript, in my opinion. I think it boils down to "a spot was missed" whenever they were implementing exactOptionalPropertyTypes. I point out exactly where that spot was missed in the reference above πŸ‘†

  5. HansBrende commented on Sep 30, 2025

    @HansBrende
    Contributor

    Axel Rauschmayer (@rauschma) this bug is now fixed, just got merged #61683!

  6. rauschma commented on Sep 30, 2025

    @rauschma
    Author
  7. locked as resolved and limited conversation to collaborators on Dec 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions