diff --git a/packages/compiler/src/frontend/lowering/containers/bytes.ts b/packages/compiler/src/frontend/lowering/containers/bytes.ts index 36e7ecd22..3722603f8 100644 --- a/packages/compiler/src/frontend/lowering/containers/bytes.ts +++ b/packages/compiler/src/frontend/lowering/containers/bytes.ts @@ -927,8 +927,9 @@ function bytesSetIntrinsic( /** `Uint8Array.prototype.set.call(target, src, offset?)`, through any typed * array constructor's prototype (they share %TypedArray%.prototype.set). A - * typed-array target lowers like `target.set(src, offset)`. Any other - * target is checked at run time: a typed array calls its `set`, and + * target the checker types as a typed array or Buffer lowers like + * `target.set(src, offset)`. Any other target, a DataView included, is + * checked at run time: a typed array calls its `set`, and * anything else throws Node's TypeError "this is not a typed array.", * after every argument is evaluated. */ export function lowerBytesPrototypeSetCall( @@ -961,7 +962,16 @@ export function lowerBytesPrototypeSetCall( if (argNodes.length < 2 || argNodes.length > 3 || argNodes.some(ts.isSpreadElement)) return null; const loc = locOf(call); const target = lowerer.lowerExpr(argNodes[0]!); - if (target.type.kind === "bytes") + // A DataView lowers to the same bytes type, so the checker type decides: + // only a typed array (or a Buffer) takes the method's path. + const targetSymbol = lowerer.typeOf(argNodes[0]!).getSymbol(); + if ( + target.type.kind === "bytes" && + targetSymbol !== undefined && + lowerer.isStdlibSymbol(targetSymbol) && + (own(BYTES_CTORS, ts.symbolName(targetSymbol)) !== undefined || + ts.symbolName(targetSymbol) === "Buffer") + ) return bytesSetIntrinsic(lowerer, target, argNodes.slice(1), loc); const slot = (value: IrExpr): { decl: IrStmt; read: IrExpr } => { const local = lowerer.declareHiddenLocal("%setArg", DYN); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8100-typed-array-prototype-set-data-view.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8100-typed-array-prototype-set-data-view.ts.json new file mode 100644 index 000000000..916caae33 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8100-typed-array-prototype-set-data-view.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8100-typed-array-prototype-set-data-view.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8100-typed-array-prototype-set-data-view.ts b/tests/corpus/8100-typed-array-prototype-set-data-view.ts new file mode 100644 index 000000000..23f3824cd --- /dev/null +++ b/tests/corpus/8100-typed-array-prototype-set-data-view.ts @@ -0,0 +1,30 @@ +// A borrowed TypedArray.prototype.set over a DataView target throws Node's +// TypeError after evaluating its arguments, and leaves the view unchanged. +const view = new DataView(new ArrayBuffer(4)); +const order: string[] = []; +function arg(label: string, value: T): T { + order.push(label); + return value; +} +try { + Uint8Array.prototype.set.call(arg("target", view), arg("source", [1, 2]), arg("offset", 1)); + console.log("set"); +} catch (e) { + console.log(e instanceof TypeError, (e as Error).message, order.join(",")); +} +console.log(view.getUint8(1), view.getUint8(2)); + +function fill(target: DataView | Uint8Array, bytes: number[]): string { + try { + Uint8Array.prototype.set.call(target, bytes); + return "set"; + } catch (e) { + return (e as Error).message; + } +} +console.log(fill(view, [7]), view.getUint8(0)); +const bytes = new Uint8Array(2); +console.log(fill(bytes, [7]), bytes[0]); +const buffer = Buffer.alloc(3); +Uint8Array.prototype.set.call(buffer, [4, 5], 1); +console.log(buffer.toString("hex"));