Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
5 tasks done
KingMario opened this issue Sep 18, 2023 · 13 comments
Closed
5 tasks done
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@KingMario
Copy link

KingMario commented Sep 18, 2023

πŸ” 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.

@MartinJohns
Copy link
Contributor

MartinJohns commented Sep 18, 2023

Duplicate of #54740, #50838, #47874.

@KingMario
Copy link
Author

Duplicate of #54740.

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

@MartinJohns
Copy link
Contributor

MartinJohns commented Sep 18, 2023

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

Also relevant: #55746

@KingMario
Copy link
Author

KingMario commented Sep 18, 2023

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.

@KingMario
Copy link
Author

KingMario commented Sep 18, 2023

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

@MartinJohns
Copy link
Contributor

MartinJohns commented Sep 18, 2023

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).

@KingMario
Copy link
Author

KingMario commented Sep 18, 2023

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.

@MartinJohns
Copy link
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. ✌️

@KingMario
Copy link
Author

@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;
};

@KingMario
Copy link
Author

KingMario commented Sep 18, 2023

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.

@jcalz
Copy link
Contributor

jcalz commented Sep 18, 2023

I agree with @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.

@KingMario
Copy link
Author

KingMario commented Sep 18, 2023

@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 @MartinJohns, both type declarations can actually meet my current needs.

@KingMario KingMario 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 Sep 18, 2023
@KingMario KingMario 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 Sep 18, 2023
@KingMario KingMario 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 Sep 18, 2023
@KingMario KingMario 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 Sep 18, 2023
@KingMario KingMario 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 Sep 18, 2023
@KingMario KingMario 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] Sep 18, 2023
@KingMario KingMario 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) Sep 18, 2023
@KingMario KingMario 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) Sep 18, 2023
@KingMario KingMario 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) Sep 18, 2023
@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Declined The issue was declined as something which matches the TypeScript vision labels Sep 18, 2023
@typescript-bot
Copy link
Collaborator

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

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Sep 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants