Skip to content

[...(T[] & { length: N; })] be instantiated as [T, T, T, ..., T](N T's in total)Β #55772

Closed as not planned

Description

πŸ” Search Terms

Array Type At Least Elements Generic

βœ… Viability Checklist

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

⭐ Suggestion

Declare an array with at least limited N elements (N = 3) is easy like:

type ArrayWith3PlusElements<T> = T[] & {
  0: T;
  1: T;
  2: T;
}

But when N is arbitury, generic support is required.

Please add feature that can declare an array with at least N elements using generic like this:

type ArrayWithNElements<T, N extends number> = T[] & {
  length: N;
};

type MyArray3 = ArrayWithNElements<number, 3>; // Array with 3 elements of type number

const arr01: MyArray3 = [1, 2, 3]; // Valid
const arr02: MyArray3 = [1, 2, 3, 4]; // Error: Array does not have 3 elements
const arr03: MyArray3 = [1, 2]; // Error: Array does not have 3 elements

type ArrayWithNPlusElements<T, N extends number> = [...ArrayWithNElements<T, N>, ...T[]];

type MyArray3Plus = ArrayWithNPlusElements<number, 3>; // Array with at least 3 elements of type number

const arr11: MyArray3Plus = [1, 2, 3]; // Valid
const arr12: MyArray3Plus = [1, 2, 3, 4]; // Valid
const arr13: MyArray3Plus = [1, 2]; // Expected to be error but actually valid: Array does not have at least 3 elements

That is, rest elements(...) operation of ArrayWithNElements<T, N> in ArrayWithNPlusElements<T, N> should effect in the type instantiation. For simplicity, instantiation is converted for ArrayWithNElements<T, N> only when the ... operation acts on it.

πŸ“ƒ Motivating Example

  1. Validation: By enforcing a minimum number of elements in an array, you can ensure that the array meets certain requirements or constraints. For example, you might want to validate that an array has at least N elements before performing certain operations on it.

  2. API Contracts: When designing APIs, you may want to specify that an array parameter should have a minimum length. This can help prevent errors or unexpected behavior when the array is used within the API.

  3. Performance Optimization: In some cases, having a minimum number of elements in an array can improve performance. For example, if you know that an algorithm requires at least N elements to work efficiently, you can enforce this constraint in the type system to ensure that the algorithm is only used with arrays of sufficient length.

  4. Code Clarity: By explicitly stating the minimum number of elements in an array type, you can make the code more self-documenting and easier to understand for other developers. It provides a clear indication of the expected array length and can help prevent mistakes or misunderstandings.

Overall, declaring a generic array type with at least N elements can help improve code correctness, maintainability, and performance in certain scenarios.

πŸ’» Use Cases

  1. What do you want to use this for?

The ability to define a generic array type with a minimum number of elements would be useful in scenarios where you want to ensure that an array meets certain requirements or constraints. For example, you might want to enforce that an array has at least N elements before performing certain operations on it.

  1. What shortcomings exist with current approaches?

The current approach in TypeScript does not support defining a type with a specific number of elements in an array. This limitation makes it challenging to enforce a minimum number of elements in an array type directly.

  1. What workarounds are you using in the meantime?

In the absence of native TypeScript support, you can consider using runtime checks or custom validation functions to ensure that an array has a minimum number of elements. These checks can be performed at runtime using conditional statements or utility functions. While this approach does not provide compile-time type safety, it can help validate the array length during runtime execution.

Activity

  1. MartinJohns commented on Sep 18, 2023

    @MartinJohns
    Contributor

    Duplicate of #54740, #50838, #47874.

  2. KingMario commented on Sep 18, 2023

    @KingMario
    Author

    Duplicate of #54740.

    I'm sorry but this is for at least N elements, not exactly N elements.

  3. MartinJohns commented on Sep 18, 2023

    @MartinJohns
    Contributor

    True, but that's more or less the same. πŸ€·β€β™‚οΈ Just [T, T, T, ...T[]] instead of [T, T, T].

    Also relevant: #55746

  4. KingMario commented on Sep 18, 2023

    @KingMario
    Author

    True, but that's more or less the same. πŸ€·β€β™‚οΈ Just [T, T, T, ...T[]] instead of [T, T, T].

    Also relevant: #55746

    But this requires N to be generic.

    And if required, readonly can be added so that the variable cannot be called with pop method or so.

  5. KingMario commented on Sep 18, 2023

    @KingMario
    Author

    Or perhaps, this behavior is buggy that arr13 is valid based on the following codes?

    type ArrayWithNElements<T, N extends number> = T[] & {
      length: N;
    };
    
    type MyArray3 = ArrayWithNElements<number, 3>; // Array with 3 elements of type number
    
    const arr01: MyArray3 = [1, 2, 3]; // Valid
    const arr02: MyArray3 = [1, 2, 3, 4]; // Error: Array does not have 3 elements
    const arr03: MyArray3 = [1, 2]; // Error: Array does not have 3 elements
    
    type ArrayWithNPlusElements<T, N extends number> = [...ArrayWithNElements<T, N>, ...T[]];
    
    type MyArray3Plus = ArrayWithNPlusElements<number, 3>; // Array with at least 3 elements of type number
    
    const arr11: MyArray3Plus = [1, 2, 3]; // Valid
    const arr12: MyArray3Plus = [1, 2, 3, 4]; // Valid
    const arr13: MyArray3Plus = [1, 2]; // Expected to be error but actually valid: Array does not have at least 3 elements
  6. MartinJohns commented on Sep 18, 2023

    @MartinJohns
    Contributor

    I have no idea what you mean with that comment.

    Regardless, you can still just adapt the solution from the linked issue:

    type TupleAtLeastMap<T> = {
      0: [...T[]],
      1: [T, ...T[]],
      2: [T, T, ...T[]],
      3: [T, T, T, ...T[]]
      // go as far as you need to
    }
    type TupleAtLeastN<N extends keyof TupleAtLeastMap<unknown>, T> = TupleAtLeastMap<T>[N];
    
    type MyArray3 = TupleAtLeastN<3, number>;
    
    const arr01: MyArray3 = [1, 2, 3]; // Valid
    const arr02: MyArray3 = [1, 2, 3, 4]; // Valid
    const arr03: MyArray3 = [1, 2]; // Error: Array does not have 3 elements

    The team is not going to add extra functionality or a utility type for this (as per the issues I linked).

  7. KingMario commented on Sep 18, 2023

    @KingMario
    Author

    What I want is a declaration with two parameter T and N, where N extends number, and If you want to declare an array with at least N elements. If N is 10,000, you don't have to write a TupleAtLeastMap for over 10,000 lines first.

  8. MartinJohns commented on Sep 18, 2023

    @MartinJohns
    Contributor

    I understand that. And I linked you an issue where someone requests almost the same, and the team explicitly declines the feature request. What makes you think your "Tuple at least N" issue is any different than the "Tuple exact N" issue?

    Anyhow, I provided you the next best solution, and now I'm outta here. ✌️

  9. KingMario commented on Sep 18, 2023

    @KingMario
    Author

    Martin Johns (@MartinJohns) Thanks.

    Tuple exact N can be declared generically with parameter T and N like this and TypeScript can validate any variable declared as such type:

    type ArrayWithNElements<T, N extends number> = T[] & {
      length: N;
    };
  10. KingMario commented on Sep 18, 2023

    @KingMario
    Author

    Giving this buggy behavior codes could express what I required more clearly:

    type ArrayWithNElements<T, N extends number> = readonly T[] & {
      length: N;
    };
    
    type NewArrayWithNElements<T, N extends number> = [...ArrayWithNElements<T, N>];
    
    type MyNewArray = NewArrayWithNElements<number, 3>;
    
    const arr11: MyNewArray = [1, 2, 3]; // Valid
    const arr12: MyNewArray = [1, 2, 3, 4]; // Expected to be error but actually valid: Array does not have 3 elements
    const arr13: MyNewArray = [1, 3]; // Expected to be error but actually valid: Array does not have 3 elements

    vs:

    type ArrayWithNElements<T, N extends number> = readonly T[] & {
      length: N;
    };
    
    type MyArray3 = ArrayWithNElements<number, 3>; // Array with 3 elements of type number
    
    const arr01: MyArray3 = [1, 2, 3]; // Valid
    const arr02: MyArray3 = [1, 2, 3, 4]; // Error: Array does not have 3 elements
    const arr03: MyArray3 = [1, 2]; // Error: Array does not have 3 elements
    image

    TypeScript TS Playground

    --

    Please note: type NewArrayWithNElements<T, N extends number> = [...ArrayWithNElements<T, N>]; are supposed to have also limited the array exactly N elements.

  11. jcalz commented on Sep 18, 2023

    @jcalz
    Contributor

    I agree with Martin Johns (@MartinJohns) that this issue will almost surely be declined. Utility types are only added if they are needed to support --declaration.


    If you want to write your own ArrayWithNPlusElements you can do so:

    type ArrayWithNPlusElements<T, N extends number, A extends T[] = []> =
      N extends A['length'] ? [...A, ...T[]] : ArrayWithNPlusElements<T, N, [...A, T]>
    
    type MyArray3Plus = ArrayWithNPlusElements<number, 3>; 
    //   ^? type MyArray3Plus = [number, number, number, ...number[]]

    Playground link

    which works for N up to 999 or so. If you need 10,000 it's probably impossible since I believe that's at or close to the limit for making programmatically generated tuple types.


    But even for 999 I sincerely doubt you really have a use case where it helps for the compiler to know this information. Let's use 50 as an example. Can you demonstrate some code where having a value of type ArrayWithNPlusElements<number, 50> is demonstrably superior to having a value of type number[]? If you are hardcoding indices like arr[40] then it will help catch things like arr[99] as errors. But if you're iterating over the array like for (let i=0; i<50; i++) { arr[i] } or computing array indices like arr[50-i] then there's no benefit because the type checker doesn't do type level math like that.

  12. KingMario commented on Sep 18, 2023

    @KingMario
    Author

    Joe Calzaretta (@jcalz) Thanks.

    This requirement comes from several functions in one project that each accepts an array as parameter and requires the array to have at least one or more elements, and the minimum number of elements required varies from function to function. These functions are passed constant parameters at call time, so I wanted to check that the minimum number of elements requirement is met for each function call at compile time rather than run time to catch errors earlier.

    The very first way to write this is to declare for each function separately the parameter like

    (T[] & {
      0: T;
      1: T;
      // and more if needed
    })

    Then I realized that I could use type ArrayWithNElements<T, N> = T[] & {length: N} to declare an array of exactly N elements, so I tried using [...ArrayWithNElements<T, N>, ...T[]] to declare an array of at least N elements but found that this declaration doesn't work in validating variables in TS playground.

    In contrast, with type TupleExactMap<T> = [T, T, T];, [...TupleExactMap<T>, ...T[]] is able to show error for array with less than 3 elements.

    image

    Playground link

    The feature I required here is that [...ArrayWithNElements<T, N>, ...T[]] would work as [...TupleExactMap<T>, ...T[]] do.

    Thanks for your working ArrayWithNPlusElements type declaration, which looks like a recursive declaration. Thanks Martin Johns (@MartinJohns), both type declarations can actually meet my current needs.

  13. changed the title [-]Declare an array with at least N elements using generic[/-] [+]rest elements(...) operation of `ArrayWithNElements<T, N>` in `ArrayWithNPlusElements<T, N>` will affect the minimum length of the array in the type instantiation[/+] on Sep 18, 2023
  14. changed the title [-]rest elements(...) operation of `ArrayWithNElements<T, N>` in `ArrayWithNPlusElements<T, N>` will affect the minimum length of the array in the type instantiation[/-] [+]rest elements(...) operation of `ArrayWithNElements<T, N>` in `ArrayWithNPlusElements<T, N>` should affect the minimum length of the array in the type instantiation[/+] on Sep 18, 2023
  15. changed the title [-]rest elements(...) operation of `ArrayWithNElements<T, N>` in `ArrayWithNPlusElements<T, N>` should affect the minimum length of the array in the type instantiation[/-] [+]rest elements(...) operation of `ArrayWithNElements<T, N>` in `ArrayWithNPlusElements<T, N>` should set the minimum length of the array in the type instantiation[/+] on Sep 18, 2023
  16. changed the title [-]rest elements(...) operation of `ArrayWithNElements<T, N>` in `ArrayWithNPlusElements<T, N>` should set the minimum length of the array in the type instantiation[/-] [+]rest elements(...) operation of `ArrayWithNElements<T, N>` in `ArrayWithNPlusElements<T, N>` should effect in the type instantiation[/+] on Sep 18, 2023
  17. changed the title [-]rest elements(...) operation of `ArrayWithNElements<T, N>` in `ArrayWithNPlusElements<T, N>` should effect in the type instantiation[/-] [+]rest elements(...) operation of `T[] & { length: N; }` should effect in the type instantiation[/+] on Sep 18, 2023
  18. changed the title [-]rest elements(...) operation of `T[] & { length: N; }` should effect in the type instantiation[/-] [+]`[...(T[] & { length: N; })]` be instantialized as `[T, T, T, ..., T]`[/+] on Sep 18, 2023
  19. changed the title [-]`[...(T[] & { length: N; })]` be instantialized as `[T, T, T, ..., T]`[/-] [+]`[...(T[] & { length: N; })]` be instantialized as `[T, T, T, ..., T]`(totally N T)[/+] on Sep 18, 2023
  20. changed the title [-]`[...(T[] & { length: N; })]` be instantialized as `[T, T, T, ..., T]`(totally N T)[/-] [+]`[...(T[] & { length: N; })]` be instantialized as `[T, T, T, ..., T]`(N T's in total)[/+] on Sep 18, 2023
  21. changed the title [-]`[...(T[] & { length: N; })]` be instantialized as `[T, T, T, ..., T]`(N T's in total)[/-] [+]`[...(T[] & { length: N; })]` be instantiated as `[T, T, T, ..., T]`(N T's in total)[/+] on Sep 18, 2023
  22. typescript-bot commented on Sep 21, 2023

    @typescript-bot
    Contributor

    This issue has been marked as "Declined" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

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

    DeclinedThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions