mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Address review: missing strings in bindings and calls, evaluated recursive flags
- A string held in a binding or returned by a call can be a missing element
read (undefined), so `head + PAD[j]` and `at(i) + PAD[j]` still add to NaN
when both are missing. Only string literals, templates and sums with such
an operand now count as always a string.
- `readdirSync(path, { recursive })` folds only the `true` and `false`
keywords. Any other flag expression, including one typed as a literal,
evaluates at run time for its effects and exceptions.
This commit is contained in:
@@ -1351,9 +1351,16 @@ export function lowerFsReaddirTypesCall(
|
||||
}
|
||||
}
|
||||
if (recursive !== null) {
|
||||
const flag = lowerer.typeOf(recursive);
|
||||
// Only the `true`/`false` keywords fold; any other expression, even one
|
||||
// typed as a literal, evaluates at run time for its effects.
|
||||
let flag = recursive;
|
||||
while (ts.isParenthesizedExpression(flag)) flag = flag.expression;
|
||||
const literal =
|
||||
flag.flags & ts.TypeFlags.BooleanLiteral ? lowerer.checker.typeToString(flag) : null;
|
||||
flag.kind === ts.SyntaxKind.TrueKeyword
|
||||
? "true"
|
||||
: flag.kind === ts.SyntaxKind.FalseKeyword
|
||||
? "false"
|
||||
: null;
|
||||
if (sawWithFileTypes && literal !== "false") {
|
||||
lowerer.noLowering(
|
||||
`${operation} with withFileTypes and recursive`,
|
||||
|
||||
@@ -6011,12 +6011,19 @@ export class Lowerer {
|
||||
if (!primitive(e.left) || !primitive(e.right)) return null;
|
||||
// A missing read is undefined, and `+` yields a number only when
|
||||
// neither operand is a string: an operand that is always a string
|
||||
// (`"x-" + PAD[i]`) makes the result a string. A read, or a sum or
|
||||
// call whose result may itself be NaN, is not always a string.
|
||||
const definitelyString = (part: ts.Expression): boolean =>
|
||||
!stringRead(part) &&
|
||||
this.mapTypeOf(this.typeOf(part))?.kind === "string" &&
|
||||
optionalPrimitiveResultType(part) === null;
|
||||
// (`"x-" + PAD[i]`) makes the result a string. Only literals,
|
||||
// templates and sums with such an operand are always strings; a
|
||||
// binding or call can hold a missing read.
|
||||
const definitelyString = (part: ts.Expression): boolean => {
|
||||
const p = peel(part);
|
||||
if (ts.isStringLiteral(p) || ts.isNoSubstitutionTemplateLiteral(p)) return true;
|
||||
if (ts.isTemplateExpression(p)) return true;
|
||||
return (
|
||||
ts.isBinaryExpression(p) &&
|
||||
p.operatorToken.kind === ts.SyntaxKind.PlusToken &&
|
||||
(definitelyString(p.left) || definitelyString(p.right))
|
||||
);
|
||||
};
|
||||
if (definitelyString(e.left) || definitelyString(e.right)) return null;
|
||||
return { kind: "union", unionId: this.unions.intern([F64, STRING]) };
|
||||
};
|
||||
|
||||
@@ -27,6 +27,22 @@ for (const flag of [false, true]) {
|
||||
}
|
||||
console.log(readdirSync(join(root, "empty"), { recursive: true }).length);
|
||||
|
||||
// A flag expression typed `true` or `false` still evaluates, once.
|
||||
let probes = 0;
|
||||
function yes(): true {
|
||||
probes++;
|
||||
return true;
|
||||
}
|
||||
function no(): false {
|
||||
probes++;
|
||||
return false;
|
||||
}
|
||||
console.log(
|
||||
readdirSync(join(root, "b"), { recursive: yes() }).length,
|
||||
readdirSync(join(root, "b"), { recursive: no() }).length,
|
||||
probes,
|
||||
);
|
||||
|
||||
try {
|
||||
readdirSync(join(root, "missing"), { recursive: true });
|
||||
} catch (e) {
|
||||
|
||||
@@ -25,3 +25,18 @@ function pair(i: number, j: number): string {
|
||||
}
|
||||
const both = pair(8, 9);
|
||||
console.log(pair(0, 1), typeof both, String(both));
|
||||
|
||||
// A missing element held in a binding or returned by a function is undefined,
|
||||
// so two of them still add to NaN.
|
||||
function at(i: number): string {
|
||||
return PAD[i];
|
||||
}
|
||||
function viaBinding(i: number, j: number): string {
|
||||
const head = PAD[i];
|
||||
return head + PAD[j];
|
||||
}
|
||||
function viaCall(i: number, j: number): string {
|
||||
return at(i) + PAD[j];
|
||||
}
|
||||
console.log(viaBinding(0, 1), String(viaBinding(5, 6)), typeof viaBinding(5, 6));
|
||||
console.log(viaCall(0, 2), String(viaCall(5, 6)), viaBinding(0, 9));
|
||||
|
||||
Reference in New Issue
Block a user