mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-05 08:13:06 +08:00
Convert booleans with ToNumber in bitwise operators
&, |, ^, <<, >> and >>> accept boolean operands, converted as ToNumber does (true 1, false 0), as JavaScript dependencies write them (async-sema's `isFn(pauseFn) ^ isFn(resumeFn)`). Hoisted JavaScript vars that may also hold undefined or null convert those to 0, which is what ToInt32 makes of NaN and 0. Number operands lower exactly as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
73a595692f
commit
8b4dc59819
@@ -1981,8 +1981,56 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
|
||||
case ts.SyntaxKind.LessThanLessThanToken:
|
||||
case ts.SyntaxKind.GreaterThanGreaterThanToken:
|
||||
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: {
|
||||
if (!bothNum)
|
||||
// Booleans take part as ToNumber converts them, true to 1 and false
|
||||
// to 0: JavaScript code writes `isFn(a) ^ isFn(b)` and `flags | ok`.
|
||||
// A hoisted JavaScript `var` can also hold undefined, and ToInt32
|
||||
// maps ToNumber(undefined) (NaN) and ToNumber(null) to 0.
|
||||
const optionalBoolTag = (e: IrExpr): number => {
|
||||
if (e.type.kind !== "union") return -1;
|
||||
const arms = lowerer.unions.get(e.type.unionId)?.arms ?? [];
|
||||
const ok = arms.every(
|
||||
(a) => a.kind === "bool" || a.kind === "undefinedT" || a.kind === "nullT",
|
||||
);
|
||||
return ok ? arms.findIndex((a) => a.kind === "bool") : -1;
|
||||
};
|
||||
const numeric = (e: IrExpr): boolean =>
|
||||
e.type.kind === "f64" || e.type.kind === "bool" || optionalBoolTag(e) >= 0;
|
||||
const boolBits = !bothNum && numeric(arithmeticLeft) && numeric(arithmeticRight);
|
||||
if (!bothNum && !boolBits)
|
||||
operatorUnsupported(lowerer, expr, ts.tokenToString(op) ?? "?", [left.type, right.type]);
|
||||
const bitPrefix: IrStmt[] = [];
|
||||
const oneOrZero = (cond: IrExpr): IrExpr => ({
|
||||
kind: "ternary",
|
||||
cond,
|
||||
then: numLit(1, loc),
|
||||
else_: numLit(0, loc),
|
||||
type: F64,
|
||||
loc,
|
||||
});
|
||||
// The tag test reads an operand twice: when either side needs it,
|
||||
// both sides evaluate once into temporaries, in source order.
|
||||
const stash = optionalBoolTag(arithmeticLeft) >= 0 || optionalBoolTag(arithmeticRight) >= 0;
|
||||
const stable = (e: IrExpr): IrExpr => {
|
||||
if (!stash || isSafeToRepeat(e)) return e;
|
||||
const tmp = lowerer.declareHiddenLocal("%bits", e.type);
|
||||
bitPrefix.push({ kind: "varDecl", localId: tmp.id, init: e, loc });
|
||||
return varRef(tmp.id, e.type, loc);
|
||||
};
|
||||
const bitOperand = (operand: IrExpr): IrExpr => {
|
||||
const e = stable(operand);
|
||||
if (e.type.kind === "bool") return oneOrZero(e);
|
||||
const tag = optionalBoolTag(e);
|
||||
if (tag < 0 || e.type.kind !== "union") return e;
|
||||
const unionId = e.type.unionId;
|
||||
return {
|
||||
kind: "ternary",
|
||||
cond: { kind: "unionIsTag", unionId, tag, negated: false, value: e, type: BOOL, loc },
|
||||
then: oneOrZero({ kind: "unionNarrow", unionId, tag, value: e, type: BOOL, loc }),
|
||||
else_: numLit(0, loc),
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
};
|
||||
const bitOp =
|
||||
op === ts.SyntaxKind.AmpersandToken
|
||||
? "&"
|
||||
@@ -1995,14 +2043,17 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
|
||||
: op === ts.SyntaxKind.GreaterThanGreaterThanToken
|
||||
? ">>"
|
||||
: ">>>";
|
||||
return {
|
||||
const bits: IrExpr = {
|
||||
kind: "bin",
|
||||
op: bitOp,
|
||||
left: arithmeticLeft,
|
||||
right: arithmeticRight,
|
||||
left: bitOperand(arithmeticLeft),
|
||||
right: bitOperand(arithmeticRight),
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
return bitPrefix.length === 0
|
||||
? bits
|
||||
: { kind: "seqExpr", stmts: bitPrefix, result: bits, type: F64, loc };
|
||||
}
|
||||
case ts.SyntaxKind.EqualsEqualsEqualsToken:
|
||||
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6550-npm-boolean-bitwise/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Bitwise operators over booleans in a JavaScript dependency convert them
|
||||
// with ToNumber, true to 1 and false to 0, as Node does (async-sema's
|
||||
// `isFn(pauseFn) ^ isFn(resumeFn)` option check).
|
||||
import { Sema, bits } from 'token-sema';
|
||||
|
||||
const plain = new Sema(3);
|
||||
const paired = new Sema(2, { pauseFn: () => {}, resumeFn: () => {} });
|
||||
console.log(plain.capacity, plain.pausable, paired.capacity, paired.pausable);
|
||||
try {
|
||||
new Sema(1, { pauseFn: () => {} });
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
}
|
||||
console.log(bits([1, 0, 3]));
|
||||
console.log(bits([]));
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export declare class Sema {
|
||||
constructor(capacity: number, options?: { pauseFn?: () => void; resumeFn?: () => void });
|
||||
readonly capacity: number;
|
||||
readonly pausable: number;
|
||||
}
|
||||
export declare function bits(values: number[]): string;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// The option check from async-sema (MIT): pauseFn and resumeFn must be given
|
||||
// together, tested with `^` over two booleans. Bitwise operators convert
|
||||
// booleans with ToNumber.
|
||||
'use strict';
|
||||
function isFn(x) {
|
||||
return typeof x == 'function';
|
||||
}
|
||||
class Sema {
|
||||
constructor(capacity, { pauseFn, resumeFn } = {}) {
|
||||
if (isFn(pauseFn) ^ isFn(resumeFn)) throw new Error('pauseFn and resumeFn must be both set for pausing');
|
||||
this.capacity = capacity;
|
||||
this.pausable = isFn(pauseFn) & isFn(resumeFn);
|
||||
}
|
||||
}
|
||||
function bits(values) {
|
||||
var yes = values.length > 0;
|
||||
var no = values.length < 0;
|
||||
var flags = 0;
|
||||
var count = 0;
|
||||
for (var v of values) {
|
||||
flags = flags | (v > 0);
|
||||
count = count + ((v > 1) & 1);
|
||||
}
|
||||
return [
|
||||
yes ^ no, yes ^ yes, yes & no, yes | no, no | no, yes & 3, 2 | yes,
|
||||
yes << 3, yes >> 0, no >>> 0, -1 >>> yes, 5 ^ yes, no ^ 7,
|
||||
typeof (yes ^ no), flags, count,
|
||||
].join(' ');
|
||||
}
|
||||
module.exports = { Sema: Sema, bits: bits };
|
||||
+1
@@ -0,0 +1 @@
|
||||
{ "name": "token-sema", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" }
|
||||
Reference in New Issue
Block a user