-
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
ArrayBuffer is not eliminated from union as expected in narrowing of ArrayBuffer | Uint8Array #50714
Comments
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 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): import fs from "node:fs/promises";
async function writeData(data: Uint8Array | ArrayBuffer): Promise<void> {
data = new Uint8Array(data);
await fs.writeFile("./some-file", data);
} Like why is |
Okay, I see. So maybe the minimal repro here should be something like function test(x: Uint8Array) {
const y: Uint8Array | ArrayBuffer = x; // okay
y // still Uint8Array | ArrayBuffer, no union narrowing here?!
const z: 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. |
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
Changes: - Updates the names of variables and parameters that deal with the tag IDs/types to all be consistently named `type`, rather than `tag`. I'm trying to make it consistent to where I refer to where the raw tag prefix be referenced as the tag type, and the live value itself be the tag. - Moved the internal private `TextEncoder()` off of the writer class, and onto it's own local constant within the writing module. This makes it consistent to the `TextDecoder()` over in the write module. - Did some other formatting and renaming too, along to match the same changes as above. Fixes: - The resulting Uint8Array from the `NBTWriter` now slices the allocated data like it used to. Using `subarray()` isn't the right fit here, as I do actually want to return an `ArrayBuffer` with the new size of the slice, rather than a smaller view on the same data. Was going to try adding support for accepting both either the `ArrayBufferLike` or `Uint8Array` types, but looks like TypeScript doesn't have enough type information to decipher the difference between the two when declaring them in a union type, unfortunately. This repo issue covers it nicely: microsoft/TypeScript#50714 Turns out I also noticed this a while back when attempting to assign a `Uint8Array` to a `DataView`, which passes the type checker's concerns, but is not supported on the runtime side, and is invalid. It's because `ArrayBuffer` isn't strictly-typed-differently enough to compare against the `TypedArray` types, as they are similar enough to both pass, which is a coincidence/overlap kind of thing. They have similar properties, which both have the same type declarations, but not specified enough to discern each other from the other. This is the same issue that I ran into with my Primitive Class constructors, but I figured out how to prevent that from working by defining the `[Symbol.toStringTag]` property. `ArrayBuffer` does this as a `string`, while `Uint8Array` is a constant `"Uint8Array"`. So, in the TS side of things, `Uint8Array`s will validate where you can use `ArrayBuffer`s, but not the other way around. I saw that you can try to use `interface` narrowing to locally add a custom `toStringTag` definition for `ArrayBuffer`, but that didn't work for me. It's a bit hectic just for handling all of that still, so I'm gonna hold off trying to add that for a little bit. Looks like `fs.writeFile()` accepts `TypedArray`s and not raw `ArrayBuffer`s, interestingly, so maybe I don't need to support raw `ArrayBuffer`s either. It does seem to be more standard to use `TypedArray`s for API design IO. The only one that is different from the others is `fetch()`, which returns raw `ArrayBuffer`s. I think it would be great to just call `fetch("./bigtest/nbt").then(response => response.arrayBuffer()).then(NBT.read)`, rather than having to add a `new Uint8Array()` constructor in the middle there. So I think I'll probably get around to just accepting both, since in the end it does make things easier to work with. I'm gonna look more into it in the meantime before directly implementing it :) Have had barely any issues with TypeScript thus far, and this one is kind of minute. Very impressed! The other thing that I want to make a feature request for, which isn't a feature yet, is to add `satisfies` support to `interface` declarations, that way you can type check a new `interface` against another `interface` which has an index signature. This would prevent the type checked interface from allowing any key, because it doesn't have the index signature *on it*. It's only using it to check it. That would be sick!!!! I'd use it like this, ideally: `interface BedrockLevelDat {} satisfies CompoundTag` I love documenting all of this, it's nice to write all of it out.
Came up with a great solution! I'm using this in my own library at the moment.
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, // global.d.ts
declare global {
interface ArrayBuffer {
toString(): "[object ArrayBuffer]";
}
}
export {}; Another note: I defined this inside of my project's |
Bug Report
π Search Terms
arraybuffer, narrow, union
π Version & Regression Information
β― Playground Link
Playground link with relevant code
π» Code
π Actual behavior
Currently ArrayBuffer is not eliminated from the union, so
data
being passed tofs.writeFile
is not of the right type.π Expected behavior
I expected the conditional to remove
ArrayBuffer
from the union, makingdata
's typeUint8Array
(after the conditional).The text was updated successfully, but these errors were encountered: