-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Wrongly infers any[] from Array.prototype.flat, Array.prototype.concat #29604
Comments
|
@fatcerberus Indeed, that explains the unsoundness problem with |
I think the |
Yes, I was right: Calling it this way instead produces the expected type: const b: boolean[] = [17].concat([19], [21]); // type error! As for To illustrate the above point: declare function f<T>(a: T, b: T, c: T): T;
/* at this point one might expect T to be inferred as string | number | boolean,
* but instead: */
f("pig", 812, true); // ERROR: 812 not assignable to type "pig"
/* ...it's inferred as string! */ I don't think that's a bug, just a limitation in how the type inference machinery works. |
I’m pretty new to TypeScript, but that seems like a bug to me.
Yeah but that doesn’t help for my actual use case, which is (I suppose I could write
Fine, but why is there a generic |
I don't know what else you could type
Because the alternative would be to produce a type error for the call in question (regardless of what it's being assigned to!) due to the aforementioned design limitation. |
https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L1355 |
To play devil's advocate for a bit, I suppose one could make the case for a generic overload of concat such as: concat<U>(...args: U[][]): (T | U)[] but thanks to type inference that would be very easy to abuse and accidentally create not-at-all-typesafe code. |
@fatcerberus A bug that one can explain as an unfortunate interaction between several possibly deliberate features may still a bug. There shouldn't be a fundamental reason that there's no way to safely type a simple array flattening operation. (Forgive me if I missed some way you might be speaking on behalf of the TypeScript team; I believe you're trying to be helpful but your GitHub profile doesn't show an official association.) The whole point of using Could |
I'm not a TypeScript team member but I do know that soundness isn't really a design goal of TS (even with the strict options in play). So if a completely sound type system what you're ultimately looking for, I'd expect some pushback from the actual team members. Just as fair warning. 😄 See |
@fatcerberus Don’t worry, like I said, I know what TypeScript is and isn’t and I would accept a reasoned conclusion that we can’t do better, in this case—but I still think it’s more likely than not that we can, in this case. As further evidence that this ought to be worth at least considering, Flow correctly infers |
One option that would improve type safety would be to change the |
On the other hand, there is a practical reason I can think of for the I think a more comprehensive solution to this problem and others like it would be to have a flag (opt-in) that made |
@fatcerberus Huh? TypeScript is supposed to be a superset of JavaScript syntactically, but it never pretends to be anything like a superset in the sense that you’ve described. TypeScript rejects all manner of valid JavaScript code like |
I could get into a big debate about the merits of gradual typing here, but I won't--I'll just point out that the technical side of this issue is the existence of This is relevant because it relies entirely on type inference (or optional JSDoc comments, but not everybody has them): therefore, if there are known gaps in the type inference mechanism, they must be filled with |
@fatcerberus I don’t understand why you still think I’m questioning the merits of gradual typing. By the way, three of the four snippets in my last comment ( Let’s please go back to the actual code that this issue is about. TypeScript gives As for |
Edge cases for I presume the rationale for the typing of |
With the above being said, it would be very nice if the compiler could catch the fallback overload being used (with The distinction between bug and design limitation is important, as it effects how much effort is needed to fix it. One is just a code change, the other involves engineering work. 😃 |
@andersk I apologize if I’ve come off as patronizing; it isn’t my intent. It’s just that sometimes there are a lot of unforeseen issues in play (corner cases, etc.) and it’s hard to be sure both sides understand them all. On the design point (and again, disclaimer: not on the TS team): because TypeScript advertises itself as having “optional typing”, I think the design as a whole favors type inference over explicit annotations, and in cases where type inference can’t infer a meaningful type it generally prefers to step aside rather than actively getting in your way. The |
Like I just said, that logic doesn’t apply to // @ts-check
const a = [[17], ["foo"]];
/** @type {(number | string)[][]} */
const b = a;
b.flat() // inferred (string | number)[] so it doesn’t apply anyway. (Please feel free to email me privately if you’d like to further lecture me with your ideas about TypeScript’s goals and the merits of gradual typing and the difference between bugs and design limitations. I assure you, it’s a waste of comment space here.) |
I'm really not trying to argue that this behavior is ideal, if that's what you think. Quite the opposite. All I'm saying is fixing it isn't necessarily trivial. For
If there was an obvious easy fix for this, sure, I would be all over it. // all of these would be type errors
Array.prototype.push("foo");
Array.prototype.push("bar");
Array.prototype[2] = "baz"; at the cost of disabling use of Putting any speculation on how TypeScript is designed aside, as a user of TypeScript I still care about this kind of thing. Fixes that look trivial on the surface, made carelessly, can break valid use cases in unforeseen ways, especially changes to typing in a language whose type system is Turing complete (which doesn't remotely apply in this case, but I needed an excuse to throw it in 😸 so don't kill me please? 🙏) |
Anyway, I've put in my 2c (there, um, may have been |
@andersk @fatcerberus any interest in writing a TL;DR of this thread? |
@RyanCavanaugh The goalposts didn’t move as a result of the discussion AFAIK, the issue is still as described in the OP. |
The array element type should be unwrappable pretty easily: type Elem<X> = X extends (infer Y)[] ? Y : X;
interface Array<T> {
flat(depth: 7): Elem<Elem<Elem<Elem<Elem<Elem<Elem<Elem<this>>>>>>>>[];
flat(depth: 6): Elem<Elem<Elem<Elem<Elem<Elem<Elem<this>>>>>>>[];
flat(depth: 5): Elem<Elem<Elem<Elem<Elem<Elem<this>>>>>>[];
flat(depth: 4): Elem<Elem<Elem<Elem<Elem<this>>>>>[];
flat(depth: 3): Elem<Elem<Elem<Elem<this>>>>[];
flat(depth: 2): Elem<Elem<Elem<this>>>[];
flat(depth?: 1): Elem<Elem<this>>[];
flat(depth: 0): Elem<this>[];
flat(depth?: number): any[];
} If I do so then I get correct errors: const x = [1, [[2]]];
const y: never = x.flat(); produces
|
I happened to end up with a similar problem when playing with Metaprogramming #14833 (comment) |
Rolling this one into #36554 |
TypeScript Version: 3.2.4, 3.3.0-dev.20190126
Search Terms: array, flat, concat, any, unsound
Code
Using
tsc --strict -t esnext
.(What I’m really trying to do: find a type safe way to concatenate an array of arrays. It’d be nice if
[].concat(...arrays)
were accepted, which I think would be a consequence of #26976, but that’s not the issue I’m reporting here.arrays.flat()
andArray.prototype.concat(...arrays)
are accepted but apparently provide no type safety.)Expected behavior:
[[17], ["foo"]].flat()
should be typed(number | string)[]
(if it’s accepted at all), andArray.prototype.concat([17], [19], [21])
should be typednumber[]
. In both cases the assignment toboolean[]
should be rejected.Actual behavior: TypeScript infers
any[]
for both right hand sides (even with--noImplicitAny
implied by--strict
), and accepts the assignments toboolean[]
with no complaints.Playground Link: Playground doesn’t support
esnext
, but here’s the second line.Related Issues: #19535 for
concat
(but that one may have more to do with the difficulty of typingconcat
’s weird behavior when non-arrays are passed?), #26976The text was updated successfully, but these errors were encountered: