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

Poor tuple support #20899

Closed
Zarel opened this issue Dec 26, 2017 · 8 comments
Closed

Poor tuple support #20899

Zarel opened this issue Dec 26, 2017 · 8 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@Zarel
Copy link

Zarel commented Dec 26, 2017

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)[]'.
@DanielRosenwasser
Copy link
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.

@Zarel
Copy link
Author

Zarel commented Dec 27, 2017

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

@michaeljota
Copy link

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.

@Zarel
Copy link
Author

Zarel commented Dec 27, 2017

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.

@Zarel
Copy link
Author

Zarel commented Dec 27, 2017

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

@sandersn
Copy link
Member

sandersn commented Jan 2, 2018

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

@Zarel
Copy link
Author

Zarel commented Jan 2, 2018

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

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Jan 2, 2018
@RyanCavanaugh
Copy link
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.

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

5 participants