You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Empty array literals without contextual type can lead to unsound index accessesย #57419
never element type allows for just anything to happen here since never is assignable to everything
๐ Expected behavior
I wouldn't expect this empty array literal to allow for this. Maybe it would be better to use an empty tuple in that situation? This would prevent obtaining never by indexing this array and thus it would prevent such accidents.
This has always bothered me too.
If I create an empty array, it's perfectly reasonable to add to it, but probably a mistake to try to get an element out of it.
That aligns better with the semantics of Array<unknown> instead of Array<never>.
The problem is that if you infer unknown[] for [] then it basically becomes unassignable to anything you'd want to assign it to:
constx: number[]=[];// error because unknown[] isn't assignable to number[]
Well, not exactlyunknown[] but something that decays to it if a better type canโt be inferred. Edit: not sure if this is contextual typing or apparent typing or a free type parameter or what-have-you.
Iโm modeling my expectation on how the return type of Array.of() works with no arguments. Its return type is T[] and can be assigned to an array of any type, but when initializing a variable, that variable becomes typed as unknown[].
Iโm modeling my expectation on how the return type of Array.of() works with no arguments. Its return type is T[] and can be assigned to an array of any type, but when initializing a variable, that variable becomes typed as unknown[].
This one uses inference from a contextual return type to achieve this effect:
It is a more specific situation here since inference is involved and this example just happens to work this way. It doesn't have anything to do with arrays and array literal expressions (and their "fresh" types).
Well, not exactly unknown[] but something that decays to it if a better type canโt be inferred.
Yeah, I would imagine this being a possibility here. I understand how it would be kind of a sidestep from general rules in how all of this currently works. On the other hand, if [][number] is valid then [][0] not being allowed is already some kind of an existing exception. I'm not sure where the line is though - I feel like I've read both "arrays are not that special in the type system" and "arrays are special in the type system" on different occasions ๐
That's what noUncheckedIndexedAccess is for. When you enable it you end up with undefined, not never.
That is fine but I think that people mostly understand that this comes into play when dealing with string[] or with an explicitnever[]. The surprising bit is that we get an implicit never[] here and we might end up with problems down the road because of that. I feel like those are just 2 - even if related - behaviors.
Pinging Ryan Cavanaugh (@RyanCavanaugh) back here because I'm almost positive I remember him explaining the exact reasoning for the never[] inference in the past. I feel like it probably has something to do with array types being assumed to be covariant and non-mutating, like every other bit of weirdness around array typing.
For types given to inferred variables, the situation is much tricker due to evolving arrays. But object properties don't qualify for evolving arrays, so the situation is analogous:
This call is processed the same way: obj.a is never[]. This one is probably the most important to get right since there is no contextual typing to "fix" the type of [] in this example.
That can be terrifically unsound since it doesn't check whether the type is being used contravariantly. The element type of the array is bounded below by never, but the upper is not known.
constobj={a: []}constpushA=<T,>(blah: {a: T[]},a:T)=>{blah.a.push(a)}pushA(obj,4)pushA(obj,"foo")// obj.a is now an array containing [4, 'foo'] but typed as never[]
Then again, this does work in a covariant context: const obj = { a: [] as const } which is the common case that never[] is supposed to deal with.
Activity
MartinJohns commentedon Feb 15, 2024
That's what
noUncheckedIndexedAccessis for. When you enable it you end up withundefined, notnever.RyanCavanaugh commentedon Feb 15, 2024
This doesn't really help much, since
[][number]is stillnever.rotu commentedon Feb 16, 2024
This has always bothered me too.
If I create an empty array, it's perfectly reasonable to add to it, but probably a mistake to try to get an element out of it.
That aligns better with the semantics of
Array<unknown>instead ofArray<never>.fatcerberus commentedon Feb 16, 2024
The problem is that if you infer
unknown[]for[]then it basically becomes unassignable to anything you'd want to assign it to:rotu commentedon Feb 16, 2024
Well, not exactly
unknown[]but something that decays to it if a better type canโt be inferred. Edit: not sure if this is contextual typing or apparent typing or a free type parameter or what-have-you.Iโm modeling my expectation on how the return type of
Array.of()works with no arguments. Its return type isT[]and can be assigned to an array of any type, but when initializing a variable, that variable becomes typed asunknown[].Andarist commentedon Feb 16, 2024
This one uses inference from a contextual return type to achieve this effect:
It is a more specific situation here since inference is involved and this example just happens to work this way. It doesn't have anything to do with arrays and array literal expressions (and their "fresh" types).
Yeah, I would imagine this being a possibility here. I understand how it would be kind of a sidestep from general rules in how all of this currently works. On the other hand, if
[][number]is valid then[][0]not being allowed is already some kind of an existing exception. I'm not sure where the line is though - I feel like I've read both "arrays are not that special in the type system" and "arrays are special in the type system" on different occasions ๐That is fine but I think that people mostly understand that this comes into play when dealing with
string[]or with an explicitnever[]. The surprising bit is that we get an implicitnever[]here and we might end up with problems down the road because of that. I feel like those are just 2 - even if related - behaviors.fatcerberus commentedon Feb 16, 2024
Pinging Ryan Cavanaugh (@RyanCavanaugh) back here because I'm almost positive I remember him explaining the exact reasoning for the
never[]inference in the past. I feel like it probably has something to do with array types being assumed to be covariant and non-mutating, like every other bit of weirdness around array typing.fatcerberus commentedon Feb 16, 2024
Nevermind, I found his explanation: #51853 (comment)
Fun aside, contextual typing for this is apparently harder than it sounds: #51853 (comment)
RyanCavanaugh commentedon Feb 16, 2024
(reading my own comment) ahh now I get it
rotu commentedon Feb 16, 2024
Bruce Pascoe (@fatcerberus)
That can be terrifically unsound since it doesn't check whether the type is being used contravariantly. The element type of the array is bounded below by
never, but the upper is not known.Then again, this does work in a covariant context:
const obj = { a: [] as const }which is the common case thatnever[]is supposed to deal with.RyanCavanaugh commentedon Feb 16, 2024
I have a lot of bad news about supertype-aliased writes in TypeScript