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
ArrayBuffer is not eliminated from union as expected in narrowing of ArrayBuffer | Uint8ArrayΒ #50714
importfsfrom"node:fs/promises";asyncfunctionwriteData(data: Uint8Array|ArrayBuffer): Promise<void>{if(datainstanceofArrayBuffer){data=newUint8Array(data);}// Argument of type 'Uint8Array | ArrayBuffer' is not assignable to parameter of type 'string | ArrayBufferView | Iterable<string | ArrayBufferView> | AsyncIterable<string | ArrayBufferView> | Stream'.// Type 'ArrayBuffer' is not assignable to type 'string | ArrayBufferView | Iterable<string | ArrayBufferView> | AsyncIterable<string | ArrayBufferView> | Stream'.// Type 'ArrayBuffer' is missing the following properties from type 'Float64Array': BYTES_PER_ELEMENT, buffer, byteOffset, copyWithin, and 24 more.(2345)awaitfs.writeFile("./some-file",data);}
π Actual behavior
Currently ArrayBuffer is not eliminated from the union, so data being passed to fs.writeFile is not of the right type.
π Expected behavior
I expected the conditional to remove ArrayBuffer from the union, making data's type Uint8Array (after the conditional).
Duplicate of #42534, #31311, and ultimately #202. It's hard to fix this without breaking other things, apparently.
I saw those issues, but even with them, they don't explain why narrowing shouldn't work here. Like even if TS (incorrectly) assumes Uint8Array <: ArrayBuffer, the conditional data instanceof ArrayBuffer should be recognized as always true, in which case data = new Uint8Array(data) should always narrow data to Uint8Array anyway.
An even simpler case (that would be incorrect at runtime, but TS shouldn't notice) that currently fails is (and should be what TS effectively sees for the above conditional):
Like why is data still inferred as Uint8Array | ArrayBuffer once it hits the second line of the function? It should be narrowed to the subtype Uint8Array as it's clearly been set to the subtype.
Reacted by Joe Calzaretta, Brandon Bennett and Will Cline
Okay, I see. So maybe the minimal repro here should be something like
functiontest(x: Uint8Array){consty: Uint8Array|ArrayBuffer=x;// okayy// still Uint8Array | ArrayBuffer, no union narrowing here?!constz: Uint8Array=y;// error}
and the question is why the assignment fails to narrow. You might want to put the stripped down repro at the top of the issue and mention how this differs from #31311, although presumably interested parties would be reading these comments too.
So maybe this is a combination of #42534 (array buffers and typed arrays are erroneously compatible) and #47731 (subtype reduction doesn't consistently happen upon assignment)?
Came up with a great solution! I'm using this in my own library at the moment.
ArrayBuffer.toString() is simply on the prototype, inherited from Object, so there's nothing directly defined on ArrayBuffer for toString() itself.
I tried a few different ways to make it work, like a facade property definition, but that was fairly ugly, as it added a property that didn't exist at all, and it wasn't real.
This on the other hand, toString() already exists, and this simply changes the return type from string to a more specific "[object ArrayBuffer]". So happy this solved it! Hopefully it works in your use case too.
// global.d.tsdeclare global {interfaceArrayBuffer{toString(): "[object ArrayBuffer]";}}export{};
Another note: I defined this inside of my project's global.d.ts file, so I think that means it won't leak out of your own codebase, and into other type definitions. If that's not the case, definitely let me know.
Activity
jcalz commentedon Sep 11, 2022
Duplicate of #42534, #31311, and ultimately #202. It's hard to fix this without breaking other things, apparently.
Jamesernator commentedon Sep 11, 2022
I saw those issues, but even with them, they don't explain why narrowing shouldn't work here. Like even if TS (incorrectly) assumes
Uint8Array <: ArrayBuffer, the conditionaldata instanceof ArrayBuffershould be recognized as always true, in which casedata = new Uint8Array(data)should always narrowdatatoUint8Arrayanyway.An even simpler case (that would be incorrect at runtime, but TS shouldn't notice) that currently fails is (and should be what TS effectively sees for the above conditional):
Like why is
datastill inferred asUint8Array | ArrayBufferonce it hits the second line of the function? It should be narrowed to the subtypeUint8Arrayas it's clearly been set to the subtype.jcalz commentedon Sep 11, 2022
Okay, I see. So maybe the minimal repro here should be something like
and the question is why the assignment fails to narrow. You might want to put the stripped down repro at the top of the issue and mention how this differs from #31311, although presumably interested parties would be reading these comments too.
jcalz commentedon Sep 11, 2022
So maybe this is a combination of #42534 (array buffers and typed arrays are erroneously compatible) and #47731 (subtype reduction doesn't consistently happen upon assignment)?
typescript-bot commentedon Sep 18, 2022
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Param Names + ArrayBuffer Sources
Offroaders123 commentedon Dec 13, 2022
Came up with a great solution! I'm using this in my own library at the moment.
ArrayBuffer.toString()is simply on the prototype, inherited fromObject, so there's nothing directly defined onArrayBufferfortoString()itself.I tried a few different ways to make it work, like a facade property definition, but that was fairly ugly, as it added a property that didn't exist at all, and it wasn't real.
This on the other hand,
toString()already exists, and this simply changes the return type fromstringto a more specific"[object ArrayBuffer]". So happy this solved it! Hopefully it works in your use case too.Another note: I defined this inside of my project's
global.d.tsfile, so I think that means it won't leak out of your own codebase, and into other type definitions. If that's not the case, definitely let me know.