-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
The intersection of a tuple with an unrelated array is handled inconsistently #53355
Comments
Sounds like a duplicate of #50346. |
I think it's a distinct issue. My conclusion that In any case, it is not logical that I am removing |
I believe that this is the same problem as here: type Obj = { a: number } & { [key: string]: string }
type Prop = Obj["a"]
// ^? number It's just that accessing a property on an intersection prefers types of concrete properties over the types from index signatures. So no conflict here is detected (even though conceptually it is a problem and such intersections are kinda invalid but at the same time... people are using that kind of an intersection as a workaround for #17867 ) |
I think the two shouldn't be conflated because we have sound open-ended tuples ( Tuples are not objects. They are ordered, they have a set number of elements, etc. I don't think they can be handled the same way as objects. As it stands, users are forced to do special casing in order to deal with tuples so that TS doesn't have to. I think it should be the other way round, especially given that users actually can't do it properly. For instance, there is to my knowledge no way to infer the rest elements of an open-ended tuple which has been intersected with an array. The information is lost. Intersections are sometimes unavoidable. When I encounter an intersection between a tuple and In general I don't think people typically intersect arrays in ways that are abusive. Since tuples are a subtype of arrays and Similarly, when I intersect two arrays or two tuples, elements and index signatures get intersected element-wise and the index-signature reflects the content of the elements of the tuple, so I would expect to be able to mix and match and have a predictable outcome. Edit. Gee, actually this is not true
type A = [number|string, boolean|symbol] & [string|symbol, number|boolean];
type B = [A[0], A[1]] // [string, boolean]
type C = A[number] // string | number | boolean | symbol Honestly this is embarrassing. |
But tuples are objects, just as arrays (and tuples are just arrays). |
I understand why you say that but I think you are conflating the abstract structure and its concretion in a data-structure. You can model a matrix with a TypedArray, but matrices are not typed arrays: they have the concept of dimensions which is lacking in typed arrays and which would be lost if you didn't special-case operations specifically for matrices when dealing with them. Tuples are not "just arrays", they have the concept of ordering and length β and with greater reason they are not just objects. TS already acknowledges this: you can express a non-empty tuple with a type definition When you use |
This is unavoidable because the TS type system primarily aims to model the primitives that exist in JS at runtime. It doesn't usually speak in terms of higher-level abstractions, and when it does it's mostly sleight-of-hand; e.g. even |
I understand what you say, but tuples are required internally by TS to handle function arguments and a bunch of other stuff in type-level programming, so it is very much a TS construct, not only a JS primitive. |
I also want to emphasis the lack of consistency between |
It seems like we have competing invariants here. The situation is exactly analogous to the thing that is already idiomatic today in string-key-land: type P = { x: number, y: number }
type PR = P & { [s: string]: boolean }
declare const pr: PR;
const n = pr.x; // number
const s = pr["x" as string]; // boolean except that for tuples, So we could special-case tuples to do something else, but now there's an inconsistency that string key / string index signature and numeric key / numeric index signature intersections behave differently. I'm sympathetic to the case that array/tuple intersections are used differently in practice. If someone wants to put together a PR that presents a cohesive case for unifying this in a way that's unambiguously better, and doesn't cause much negative churn in real projects, I think that's something we could consider. That said, the behavior is consistent with other interactions of declared-property and index-signature property access, and as such I don't consider this a defect, this all having been the case for something like 6+ years now with this report being the first or maybe second case where someone truly objected to the behavior is it stands. |
I think there already are trivial inconsistencies between tuples and objects regarding index signatures, simply because tuples come with an index signature to begin with and it is considered during intersection:
Another example is the bounded indexation of tuples: type A = [1, 2, 3];
type B = A[number] // 1 | 2 | 3
type C = A[4] // error
// ----- This is consistent with objects in terms of the observable behaviour (can't access a key that isn't there) but it is not consistent in terms of the underlying rules because an index signature is actually there but it doesn't make the promise that every possible numeric key has a value. Intersecting the tuple with its own index signature and see the behaviour change kind of establishes that tuples are not objects: type A = [1, 2, 3] & { [k: number]: 1 | 2 | 3 };
type B = A[number]
type C = A[4] // 1 | 2 | 3
// --------- I think people are pretty tolerant concerning tuples because you have been adding feature after feature, enabling their use in more and more places and fixing issues over time. I personally consider their implementation in the language a work in progress and I don't share the "that's the way it is" attitude of my fellow commenters because they still have problems (and there are other tuple-related issues). I appreciate the open-mindedness and sympathy. |
Is this one idiomatic though? I know that this is how it works but I always considered it to be a defect/an implementation byproduct. The idiomatic invariant (to me) would be for those two to always behave the same way: type Intersected = { concrete: string } & { [key: string]: number }
type MyType = { concrete: string; [key: string]: number } But the second is not allowed (for good reasons).
To add some context, I've seen people running into this problem with tuples on multiple occasions. Often they just don't report this stuff, OTOH - the reported issues are a metric of how popular of a feature request this is... so we could definitely also say that it's not that popular of a request. I've seen people trying to start with |
Someone in the TypeScript Community Discord server brought up some odd order-dependent behavior when Anyway, while mulling it over I sketched up a bunch of potentially-surprising/inconsistent-seeming scenarios which could be worth further consideration. |
Bug Report
π Search Terms
tuple, intersection, array, unrelated, narrowing, index signature
π Version & Regression Information
This is the behavior in every version I tried
β― Playground Link
Playground link with relevant code
π» Code
π Actual behavior
The index signature is appropriately updated, but not the individual elements nor the methods operating on the resulting tuple. Additionally, methods get overloaded, so when calling
pop
for example, the first overload is selected and the return type is not the intersection of the original components return types as it should be.π Expected behavior
I would expect
ShouldBeNever
to be[never, never, never]
andShouldBeNarrowed
to be[string, boolean]
The text was updated successfully, but these errors were encountered: