Skip to content

Poor tuple support #20899

Description

@Zarel

TypeScript Version: 2.7.0-dev.20171226

Code

function getPromise(): Promise<[string, number]> {
	return Promise.resolve(['', 0]);
}

The above is an example, but it's not the only example of where TypeScript has trouble with tuples.

Others include:

let a: [string, number] = ['', 0];
let b: [string, number] = a.slice();

The above one is harder, but it'd be great if array.slice() and array.slice(0) could be used to clone tuples.

https://github.com/Zarel/Pokemon-Showdown/blob/2b5654e307d7d65ee16c8bf55961b352c97c96e9/users.js#L1438

The above linked example disappeared when I tried to construct a simple equivalent testcase:

let a = null as [string, string, object][] | null;

if (a) {
	a.push(['', '', {}]);
} else {
	a = [['', '', {}]];
}

But if you care to clone that repo, checkout that commit, and delete the // @ts-ignore, you'll get a similar error message as the others, where the tuple degrades to an array.

Expected behavior:

No errors

Actual behavior:

test1.ts(2,2): error TS2322: Type 'Promise<(string | number)[]>' is not assignable to type 'Promise<[string, number]>'.
  Type '(string | number)[]' is not assignable to type '[string, number]'.
    Property '0' is missing in type '(string | number)[]'.
test1.ts(2,5): error TS2322: Type '(string | number)[]' is not assignable to type '[string, number]'.
  Property '0' is missing in type '(string | number)[]'.
users.js(1438,4): error TS2322: Type '(string | Connection)[][]' is not assignable to type '[string, string, Connection][] | null'.
  Type '(string | Connection)[][]' is not assignable to type '[string, string, Connection][]'.
    Type '(string | Connection)[]' is not assignable to type '[string, string, Connection]'.
      Property '0' is missing in type '(string | Connection)[]'.

Activity

  1. DanielRosenwasser commented on Dec 27, 2017

    @DanielRosenwasser
    Member

    I think the general case you're seeing is a design limitation of our analyses not seeing far enough where these values get used. But see #4988 for more relevant discussion on the idea of slice() returning this.

  2. Zarel commented on Dec 27, 2017

    @Zarel
    Author

    I think I understand the problem. When inferring type, tuples should degrade to arrays, so in situations like this:

    let a = [1, 2];
    

    a is inferred to be number[] rather than [number, number], so that later, a.push(3); isn't an error.

    I think a simple workaround might be to prefer tuples to arrays of multiple type.

    So [1, 2] would be inferred as number[] rather than [number, number], but [1, ''] would be inferred as [number, string] rather than (number | string)[].

    It might be different from others, but in my experience, multi-type tuples are much more common than multi-type arrays, and I would rather specify the latter explicitly than the former.

    I guess the biggest problem with that approach might be backwards compatibility, though...

  3. michaeljota commented on Dec 27, 2017

    @michaeljota

    but [1, ''] would be inferred as [number, string] rather than (number | string)[].

    I think there is no way for the compiler to know that you what to do a tuple instead of an array. Either case, you could check the stricter tuple PR, that probably will be available in next release. #17765. Still, I don't think the compiler should mark a tuple a initialized array.

  4. Zarel commented on Dec 27, 2017

    @Zarel
    Author

    In hindsight, I think mutable array literals should be pretty rare. If I write a non-empty array literal, I usually want it to be either immutable or a tuple.

    The times I want to initialize a non-empty array and keep it mutable are rare enough that I'd probably rather have TypeScript infer it's a tuple by default and explicitly type them if I want them mutable, than have TypeScript infer it's mutable by default and explicitly type them if I want them to be a tuple.

  5. Zarel commented on Dec 27, 2017

    @Zarel
    Author

    Perhaps a compiler option to infer all non-empty array literals as tuples by default?

  6. sandersn commented on Jan 2, 2018

    @sandersn
    Member

    Guangcong Luo (@Zarel) mutable array literals are rare but we are constrained by backward compatibility for those rare cases (see #17765 for exactly such a case that we ran into with firebase). There may be other reasons to make array literals immutable, but tuple usage is also quite rare [1], so we would need other, stronger motivations.

    [1] As far as I can tell from sources like definitely typed and our user-code tests. Javascript's object literal support is close enough to tuples that it seems like most people don't bother.

  7. Zarel commented on Jan 2, 2018

    @Zarel
    Author

    I know, which is why I'm asking for it to be behind a compiler flag.

  8. RyanCavanaugh commented on Jan 2, 2018

    @RyanCavanaugh
    Member

    A flag isn't a great choice because the choice of tuple vs not is really a global decision - some arrays should be tuples, some aren't. In any case this problem isn't worth doubling the configuration space over.

  9. locked and limited conversation to collaborators on Jul 3, 2018
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

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions