Skip to content
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

Variadic with "named tuples" doesn't preserve name #39941

Closed
ChuckJonas opened this issue Aug 6, 2020 · 9 comments
Closed

Variadic with "named tuples" doesn't preserve name #39941

ChuckJonas opened this issue Aug 6, 2020 · 9 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@ChuckJonas
Copy link

Not sure this actually should be possible... I guess it would get funky as typescript would have to deal with naming conflicts.

TypeScript Version: 4.1.0-dev

Search Terms:
Variadic, Named Tuples
Code

type Append<I, T extends unknown[]> = [...T, last:I];
type FooBar = Append<string, [foo: number, bar: boolean]>

Expected behavior:
Should preserve named tuples labels:

type FooBar = [foo: number, bar: boolean, last: string]

Actual behavior:

type FooBar = [number, boolean, string]

Playground Link:
https://www.typescriptlang.org/play?ts=4.0.0-beta#code/C4TwDgpgBAgmkDsAmAeAkgGigFShAHsBMgM5QCuCA1ggPYDuCA2gLoB8UAvFEwHT-YsAGwCGJYAC40LANwAoUJCgAxWrQBCIgE5dY8YqnFaAlggDmWJgDM1EqAnIBbAEYQtWZ9rvO1QiCIR2OSA

Related Issues:
none

@DanielRosenwasser DanielRosenwasser added the Experience Enhancement Noncontroversial enhancements label Aug 11, 2020
@DanielRosenwasser DanielRosenwasser added this to the TypeScript 4.1.0 milestone Aug 11, 2020
@DanielRosenwasser DanielRosenwasser added the Needs Investigation This issue needs a team member to investigate its status. label Aug 11, 2020
@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Aug 11, 2020

@ahejlsberg any idea how feasible this would be? One problem is you don't know whether two spread instantiations will end up with identical names.

@xuld
Copy link

xuld commented Aug 21, 2020

In most use case, names should be unique.

My proposal:

[...[foo: number, bar: boolean], foo: string] // --> [foo: number, bar: boolean, foo_2: string]
[...[...[foo: number, bar: boolean], foo: string], foo: string] // --> [foo: number, bar: boolean, foo_2: string, foo_3: string]
[foo: string, ...[foo: number, bar: boolean]] // --> [foo: string, foo_2: number, bar: boolean]

@0az
Copy link

0az commented Sep 28, 2020

Setting aside the generic case, having name preservation for simpler cases would be great:

type Foo = [foo: string]
type FooBar = [...Foo, bar: string]
// src/index.ts:2:22 - error TS5084: Tuple members must all have names or all not have names.

https://www.typescriptlang.org/play?ts=4.0.2#code/C4TwDgpgBAYg9nKBeKBtAZggXFAzsAJwEsA7AcwF0AoUSWBAIQEMDk0A6T+OAGigCMWOfMXLUqAYzgl8UTHEGsUqAOTyVfFYpXUpMuABsI7A3DIAKAAYASAN7zFAX0sBKIA

This doesn't error on playground because of #40118, which landed after 4.0.2.

@szymski
Copy link

szymski commented Feb 13, 2021

I hit the same problem. I found an ugly workaround which looks like this:
type Append<A, B> = A extends [...infer Params] ? [...Params, ...(B extends [...infer Params2] ? Params2 : [])] : never;
Labels are preserved.

@ahejlsberg
Copy link
Member

We already support giving names to variadic elements, and the original example works as expected when you do so:

type Append<I, T extends unknown[]> = [...first: T, last: I];
type FooBar = Append<string, [foo: number, bar: boolean]>;  // [foo: number, bar: boolean, last: string]
type FooBaz = Append<string, number[]>;  // [...first: number[], last: string]

Note that when a variadic element is instantiated with a tuple type, the individual element names of that tuple type override the name of the variadic element.

All in all, I don't think any new features or changes are needed here.

@ahejlsberg ahejlsberg added Working as Intended The behavior described is the intended behavior; this is not a bug and removed Experience Enhancement Noncontroversial enhancements Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone labels Feb 14, 2021
@ahejlsberg ahejlsberg removed this from the TypeScript 4.2.0 milestone Feb 14, 2021
@szymski
Copy link

szymski commented Feb 14, 2021

@ahejlsberg

Note that when a variadic element is instantiated with a tuple type, the individual element names of that tuple type override the name of the variadic element.

What do you mean by that exactly?

Take a look at the following example:

type Input = [first: string, second: number];

type Append<A extends unknown[], B extends unknown[]> = [...A, ...B];
type AppendInPlaceWithSpread<A extends unknown[]> = [...A, ...[third: boolean]];
type AppendInPlaceWithoutSpread<A extends unknown[]> = [...A, third: boolean]; // Error: Tuple members must all have names or all not have names.

// Works as expected, element names are preserved
type Result1 = Append<Input, [third: boolean]>; // [first: string, second: number, third: boolean]

// Element names are lost
type Result2 = AppendInPlaceWithSpread<Input>; // [string, number, boolean]

What's the difference between Append and AppendInPlaceWithSpread? To me it seems like both do the same thing, expect one is taking the second tuple as a parameter. If there is a different behavior when a labeled tuple is instantiated in place, I think it should be mentioned in the documentation. It might confuse some users.

I also noticed that if we name the first variadic element in the following way, everything works as expected:

type Append<A extends unknown[]> = [...abc: A, third: boolean]

But here the label abc is ignored, so what's the point of defining it?

@ahejlsberg
Copy link
Member

But here the label abc is ignored, so what's the point of defining it?

The point is that the name is used when the variadic element is instantiated with an array type such as number[] as in one of my examples above. In your example here, Append<number[]> would use the label abc for the initial rest element. Note, however, that this only happens in TS 4.2 and later because earlier versions did not support leading rest elements.

In general, our rule for checking that either all or none of the elements are named isn't perfect when variadic elements are involved because we can't really know whether instantiations will provide names or not. We could potentially consider getting rid of that rule since names have no semantic meaning and it doesn't really matter if something is only partially named.

@typescript-bot
Copy link
Collaborator

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

@victorgarciaesgi
Copy link

I know it's closed but for anyone searching for a way of "patching" Variadic tuple types while keeping names I found a easy workaround!

It's basicaly assigning the typle to a new function, patching the function parameters, and returning the parameters of this function.

Exemple I had for a Vue lib:

import type {Ref} from 'vue';
type CreateFn<T extends any[]> = (...args: T) => any;

export type PatchTuple<T extends any[], F = CreateFn<T>> = Parameters<
  F extends (...args: infer Args) => any
    ? (
        ...args: {
          [K in keyof Args]: Ref<Args[K]>;
        }
      ) => any
    : never
>;

type MyTuple = [foo: string, bar?: Date];
type patched = PatchTuple<MyTuple>; // [foo: Ref<string>, bar?: Ref<Date | undefined>]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

9 participants