mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 08:35:07 +08:00
Fix Buffer.from on narrowed callback unions (#422)
- Extract the checker-proven source arm before constructing a Buffer from a callback value. - Cover narrowed byte and number array callbacks with Node differential fixtures.
This commit is contained in:
@@ -4621,7 +4621,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
// Typed-array/Buffer receivers and the Buffer statics — before the
|
||||
// island path (bytes never cross the boundary).
|
||||
lowerer.lowerBytesMethodCall(expr, expr.expression) ??
|
||||
lowerBufferStaticCallWithOptionalArg(lowerer, expr, expr.expression) ??
|
||||
lowerBufferStaticCallWithNarrowedArg(lowerer, expr, expr.expression) ??
|
||||
// URL.revokeObjectURL's zero-argument contract (the one-argument
|
||||
// form keeps the fence — createObjectURL does too).
|
||||
lowerUrlStaticCall(lowerer, expr, expr.expression) ??
|
||||
@@ -4914,25 +4914,34 @@ function optionalCallValue(lowerer: Lowerer, node: ts.Expression): IrExpr | null
|
||||
return lowerer.runtimeOptionalIdentifierValue(node)?.value ?? lowerAbsenceProbe(lowerer, node);
|
||||
}
|
||||
|
||||
function lowerBufferStaticCallWithOptionalArg(
|
||||
function lowerBufferStaticCallWithNarrowedArg(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
const lowered = lowerer.lowerBufferStaticCall(call, access);
|
||||
if (lowered?.kind !== "bytesNew" || !lowered.source || lowered.source.type.kind !== "union") return lowered;
|
||||
const present = lowerer.stripUndefinedArm(lowered.source.type);
|
||||
const validSource =
|
||||
(present.kind === "array" && present.elem.kind === "f64") ||
|
||||
(present.kind === "bytes" && present.elem === "u8");
|
||||
if (!validSource) return lowered;
|
||||
const helper = lowerer.narrowedArmHelper(lowered.source.type.unionId, present, lowered.source.loc);
|
||||
return helper
|
||||
? {
|
||||
...lowered,
|
||||
source: { kind: "call", callee: helper, args: [lowered.source], type: present, loc: lowered.source.loc },
|
||||
}
|
||||
: lowered;
|
||||
// A callback can store a wider union than the checker sees at this use
|
||||
// (including an added undefined arm). Extract the exact arm proven by
|
||||
// control-flow narrowing, not merely the union with undefined removed.
|
||||
const narrowed = lowerer.mapTypeOf(lowerer.typeOf(call.arguments[0]!));
|
||||
if (
|
||||
!narrowed ||
|
||||
!(narrowed.kind === "f64" ||
|
||||
(narrowed.kind === "array" && narrowed.elem.kind === "f64") ||
|
||||
(narrowed.kind === "bytes" && narrowed.elem === "u8")) ||
|
||||
lowerer.armTag(lowered.source.type.unionId, narrowed) < 0
|
||||
) {
|
||||
lowerer.unsupported("SC1090", call.arguments[0]!, "a Buffer constructor argument whose narrowed type is not a stored source arm");
|
||||
}
|
||||
const helper = lowerer.narrowedArmHelper(lowered.source.type.unionId, narrowed, lowered.source.loc);
|
||||
if (!helper) {
|
||||
lowerer.unsupported("SC1090", call.arguments[0]!, "a Buffer constructor argument whose narrowed type is not a stored source arm");
|
||||
}
|
||||
return {
|
||||
...lowered,
|
||||
source: { kind: "call", callee: helper, args: [lowered.source], type: narrowed, loc: lowered.source.loc },
|
||||
};
|
||||
}
|
||||
|
||||
function lowerOptionalNumberDefault(
|
||||
|
||||
@@ -6895,6 +6895,12 @@
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/3021-buffer-union-narrow-callback.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/3021-buffer-union-narrow-callback.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/303-break-continue.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/303-break-continue.ts"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
type SqlParam = string | number | boolean | null | Uint8Array;
|
||||
|
||||
function encodeParam(param: SqlParam): string | number | boolean | null | { $hex: string } {
|
||||
if (param !== null && typeof param === "object") {
|
||||
return { $hex: Buffer.from(param).toString("hex") };
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
function encodeParams(params: SqlParam[]): string {
|
||||
return JSON.stringify(params.map(encodeParam));
|
||||
}
|
||||
|
||||
console.log(encodeParams(["a", 1, true, null, new Uint8Array([0xde, 0xad])]));
|
||||
|
||||
type ListParam = string | number[] | null;
|
||||
|
||||
function encodeList(param: ListParam): string {
|
||||
if (Array.isArray(param)) return Buffer.from(param).toString("hex");
|
||||
return param === null ? "null" : param;
|
||||
}
|
||||
|
||||
const listParams: ListParam[] = [null, [1, 254], "text"];
|
||||
console.log(listParams.map(encodeList).join("|"));
|
||||
Reference in New Issue
Block a user