Skip to content

Inconsistent Behavior with Equality Check Using Pick<Readonly<T>, K> in Mapped TypeΒ #58163

Closed

Description

@Snowflyt

πŸ”Ž Search Terms

"readonly", "pick", "equality", "equal"

πŸ•— Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about Generics.

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.4.5#code/JYOwLgpgTgZghgYwgAgPLIN4ChnJge3wC5kBnMKUAcwG4sBfLLMATwAcUBRARwFc4ANgB4AGgBpkATQB8yALw5kACiEAVaUoCU82auQQAHpBAATUshHIA-MgCMyEgCZth42eRqN2ubv1GIpuaS1nYOyI4hFLwoJPACpBB0zOwoAFK85AAKwAgA1p7ymIoA2gDSyKDIuRAs+DDIqgC6JNl5ahKl0nT0dKwcyJDkAPoADIXpWTn5qNLFAEQE+HONdAD0q7jIAHpWJBh4hCTklCBUyIzJ-a25AEoQcCb4IAIsBXJFuGUVIFU1dQ3NZDXIR3B5PF6eDpdBi9FIDCDDezva6gx7PV4zeaLZZrDa4HZ7ZBQe5ol4HYhkCjUc5MPooQZgIYRd48fjCfaLI5U07nCT7Ylg9HkrknM70aHrTYEvCCBK0uEAZQocFOEDeH2QX0q1Vq9SaJFZgiEwNUUIkwNR4Ne6ih0Nwks2jqdzu2Wzd7vd+I93p9HoaAAtgOZSP78LwBCZkAAjFD8klW4WU0XnZCIBD4KAmalgfDIAAGKPj6LzigdLvLUt9btLeM2qkDwdD4cjMcwieO1PoqYQ6cz2dzeYmYGuJftGwAdJOyAPDcITWagVMQUWITbkJ1pHmyE2I9GUHm4gkt2mM1meTn8wymSXGHT4cMAMyFJVQFVUNWYhaEHFYMvSqIoEIAC0yAAOr+iwViXPSCKMgALIUs7GkuqASF+SzSOaS6WuiQgzGh2LSBKtbSoeEBYEAA

πŸ’» Code

interface O {
  foo: string;
}

type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;

type JustPick<T> = {
  [K in keyof T]: Pick<T, K>;
};
type test_0 = JustPick<O>["foo"];
//   ^?: { foo: string }

type PickReadonly<T> = {
  [K in keyof T]: Pick<Readonly<T>, K>;
};
type test_1 = PickReadonly<O>["foo"];
//   ^?: { readonly foo: string }

type test_2 = Equal<{ foo: string }, { readonly foo: string }>;
//   ^?: false

type Strange<T> = {
  [K in keyof T]: Equal<Pick<T, K>, Pick<Readonly<T>, K>>;
  //                    ^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^ This should be { readonly foo: string } according to `PickReadonly`
  //                    ^^^^^^^^^^
  //     This should be { foo: string } according to `JustPick`
  // ... so `Equal<Pick<T, K>, Pick<Readonly<T>, K>>` should be `false` according to `test_2`
}
type test_3 = Strange<O>["foo"];
//   ^?: true <- Why?
type test_4 = Equal<Pick<O, "foo">, Pick<Readonly<O>, "foo">>;
//   ^?: false

πŸ™ Actual behavior

Type of test_3 is true instead of expected false like test_2 and test_4.

πŸ™‚ Expected behavior

Type of test_3 should be false like test_2 and test_4.

Additional information about the issue

No response

Activity

  1. RyanCavanaugh commented on Apr 12, 2024

    @RyanCavanaugh
    Member

    This definition of Equal intentionally does the one thing that relies on the internal representation of types in the compiler. I really do not recommend using it. A definition that isn't reverse-engineered to actively rely on implementation details is

    type Equal<X, Y> = [X, Y] extends [Y, X] ? true : false;

    which gives consistent results

    readonly doesn't affect assignability so can't really be used in conditional types like this. Attempts to manifest other behavior will ultimately fail.

  2. Snowflyt commented on Apr 13, 2024

    @Snowflyt
    Author

    This definition of Equal intentionally does the one thing that relies on the internal representation of types in the compiler. I really do not recommend using it. A definition that isn't reverse-engineered to actively rely on implementation details is

    type Equal<X, Y> = [X, Y] extends [Y, X] ? true : false;

    which gives consistent results

    readonly doesn't affect assignability so can't really be used in conditional types like this. Attempts to manifest other behavior will ultimately fail.

    Thanks for the detailed responseβ€”it really cleared things up for me! I appreciate your help and the alternative you provided.

  3. locked as resolved and limited conversation to collaborators on Oct 22, 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

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions