Skip to content

never as keyof tuples/arrays behaves the same as number #55841

Open

Description

🔎 Search Terms

tuple keyof never, tuple[never], array keyof never

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about this issue

Versions I tried:

  • 5.2.2
  • nightly (playground)

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.3.0-dev.20230923#code/C4TwDgpgBAglC8UDeBfA2gOwgNwgJwF0oB6YqQUHIotc8AoUSKAIQWSgEMAuKAZ2DwCWGAOZR01fEVIUqOfPXDQAwqzQFMcwiTKUJdBtAAiqvoJHq9UsoBlyXvyHCFjAKKtTDtRppWot9yKdoADFWAAp-UQAfKgBXAFsAI3wASk9LKG1fOzMo2MT8IA

💻 Code

type A = {}[never] // ✅ never
type B = { a: string }[never] // ✅ never
type C = [][never] // ✅ never
type D = [string][never] // ❌ string
type E = string[][never] // ❌ string
type F = (string | number)[][never]  // ❌ string | number

🙁 Actual behavior

After using a never key on tuples/arrays you get back the entirety of the tuple/array elements type.

🙂 Expected behavior

After using a never key on tuples/arrays you get back never, as when dealing with objects.

Additional information about the issue

Not sure if this has historical reasons to behave like it does, but couldn't find any specific info about it.

Activity

  1. MartinJohns commented on Sep 23, 2023

    @MartinJohns
    Contributor

    Is there an actual issue you had with this behaviour? It sounds awfully lot like this issue and comment: #53345 (comment)

  2. kristiannotari commented on Sep 24, 2023

    @kristiannotari
    Author

    Couldn't find that one! Thanks for pointing that out.
    There are no unsolvable problems to be honest, because with your custom types you can workaround the "unexpected" behaviour quite simply.

    My actual use case is a "find if there are duplicates in tuple" type. My approach was to deconstruct the tuple with its indexes and check if the current element is assignable to one of the types in the union of the other tuple keys. What's interesting is that by simply stating this, you can see why returning the whole tuple/array element type when indexing with never doesn't seems to be "right", when manipulating tuples/arrays.

    Implementation example:

    export type Indexes<T extends readonly any[]> = keyof T & `${number}`
    
    declare namespace IfNoDuplicateElements {
        export type Duplicate = "Duplicate"
        export type NotDuplicate = "NotDuplicate"
        export type HasDuplicates<T extends readonly any[]> = {
             [K in Indexes<T>]: T[K] extends T[Exclude<Indexes<T>, K>] ? Duplicate : NotDuplicate
        }[Indexes<T>]
    }
    export type IfNoDuplicateElements<
        T extends readonly any[],
        True = T,
        False = never
    > = IfNoDuplicateElements.Duplicate extends IfNoDuplicateElements.HasDuplicates<T> ? False : True

    I'm basically mapping each element of the tuple to a "Duplicate" or "NotDuplicate" type so to check if any of the elements is duplicated.

    The HasDuplicates type, since indexing with never equals to the whole tuple elements type, requires a special case handling for it, instead of being able to apply what I'd call "a reasonable enough approach". It seems fine to me to think about the problem's solution as: "check if any tuple element is equal to any other tuple element". Encoding this logic directly, which seemed correct at first, introduces a subtle bug with 1 elements tuples, forcing a special case handling.

    Not to say this is the best approach to this problem, but surely the special case handling seems not intuitive.

    Behaviour example (without the special case handling):

    type A = IfNoDuplicateElements<[1], true, false> // ❌ false WHAT???
    type B = IfNoDuplicateElements<[1,1], true, false> // ✅ false 

    To make the impl work as intended you need to add the special case handling for 1 elements tuple:

    export type HasDuplicates<T extends readonly any[]> = T extends [any] ?  NotDuplicate : {
        [K in Indexes<T>]: T[K] extends T[Exclude<Indexes<T>, K>] ? Duplicate : NotDuplicate
    }[Indexes<T>]

    edit: pasted the wrong impl example
    edit2: playground link as reference for examples

  3. RyanCavanaugh commented on Sep 26, 2023

    @RyanCavanaugh
    Member

    Happy to take a PR on this one now that we have an example of where it might matter

  4. jfet97 commented on Sep 27, 2023

    @jfet97
    Contributor

    Ryan Cavanaugh (@RyanCavanaugh) I closed my PR because in the past a similar issue was closed as "Working as Intended". But now that this issue has been marked as a "Possible Improvement" I re-opened it: #55842. Could you check it out?

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

    Domain: Indexed Access TypesThe issue relates to accessing subtypes via index accessPossible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some cases

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions