Infer array as tuple when used as rest argument in a complex expression
#56,765 opened on Dec 13, 2023
Repository metrics
- Stars
- (48,455 stars)
- PR merge metrics
- (Avg merge 6d 17h) (9 merged PRs in 30d)
Description
🔍 Search Terms
"infer rest array type" "infer rest array args" "infer rest array tuple" "spread argument array expression "
✅ 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 isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion
Given the following function
function foo (a: number, b: string, c: boolean) {}
TypeScript already supports inferring that an array used as rest argument should be considered as a tuple:
foo (1, ...(["a", true])) // Works
But if you add some condition to the mix, type inference breaks:
const someCondition = true
foo (1, ...(someCondition ? ["a", true] : ['b', false])) // A spread argument must either have a tuple type or be passed to a rest parameter.(2556)
My suggestion it to add support for inlined array inside complex expression to be used as spread args.
📃 Motivating Example
I came accros this issue while designing a database agnostic module based on kysely.
I used to write stuff like so:
const myTableWithId =
dialect === 'pg'
? db.schema
.createTable('my_table')
.addColumn('id', 'bigserial', (col) => col.primaryKey())
: db.schema
.createTable('my_table')
.addColumn('id', 'bigint', (col) => col.autoIncrement().primaryKey())
await myTableWithId
.addColumn('my_col', 'varchar', (col) => col.notNullable())
.execute()
But this breaks the linear style of the definition. The following notation is more natural:
await db.schema
.createTable('oauth_device')
.addColumn(
'id',
...(dialect === 'pg'
? ['bigserial', (col) => col.primaryKey()]
: ['bigint', (col) => col.autoIncrement().primaryKey()])
)
.addColumn('my_col', 'varchar', (col) => col.notNullable())
.execute()
It currently work but requires additional typecasting (see playground link before)
💻 Use Cases
- What do you want to use this for? Be able to write more readable code
- What shortcomings exist with current approaches? Unnecessarily more verbose
- What workarounds are you using in the meantime? Declare the type of the spread tuple and cast every value to that type