fix(javascript): isolate lexical loop bindings (#511)
* fix(javascript): isolate lexical loop bindings Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> * test(javascript): isolate loop lexical names regression cases Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> --------- Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
parent
6b7923cdd2
commit
c30878f616
|
|
@ -166,6 +166,66 @@ describe("JavaScript semantic analysis: structure 1", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("JavaScript semantic analysis: loop lexical names", () => {
|
||||
it.each([
|
||||
`for (let value = "inside"; ready; step()) { consume(value); }`,
|
||||
`for (const value of entries) { consume(value); }`,
|
||||
`for (const value in entries) { consume(value); }`,
|
||||
])("does not leak lexical loop bindings into the enclosing scope", (loop) => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const value = "outside";
|
||||
${loop}
|
||||
consume(value);
|
||||
`);
|
||||
expect(topLevelBinding(ir, "value").value).toEqual({
|
||||
status: "literal",
|
||||
value: "outside",
|
||||
});
|
||||
expect(bindingsNamed(ir, "value")).toHaveLength(2);
|
||||
const reads = ir.references.filter(
|
||||
({ name, role }) => name === "value" && role === "read",
|
||||
);
|
||||
expect(reads).toHaveLength(2);
|
||||
expect(reads[0]?.bindingId).not.toBe(reads[1]?.bindingId);
|
||||
expect(reads[1]?.bindingId).toBe(topLevelBinding(ir, "value").bindingId);
|
||||
});
|
||||
|
||||
it("resolves closures inside a loop against the loop binding", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const value = "outside";
|
||||
for (const value of entries) { callbacks.push(() => value); }
|
||||
consume(value);
|
||||
`);
|
||||
const reads = ir.references.filter(
|
||||
({ name, role }) => name === "value" && role === "read",
|
||||
);
|
||||
expect(reads).toHaveLength(2);
|
||||
expect(reads[0]?.bindingId).not.toBe(
|
||||
topLevelBinding(ir, "value").bindingId,
|
||||
);
|
||||
expect(reads[1]?.bindingId).toBe(topLevelBinding(ir, "value").bindingId);
|
||||
expect(ir.closureCaptures).toContainEqual(
|
||||
expect.objectContaining({
|
||||
bindingId: reads[0]?.bindingId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps var loop declarations in the enclosing function", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
function run() { for (var value of entries) { consume(value); } return value; }
|
||||
`);
|
||||
const binding = onlyBinding(ir, "value");
|
||||
const callable = ir.callables.find(({ name }) => name === "run");
|
||||
expect(binding.scopeId).toBe(callable?.bodyScopeId);
|
||||
expect(
|
||||
ir.references
|
||||
.filter(({ name, role }) => name === "value" && role === "read")
|
||||
.every(({ bindingId }) => bindingId === binding.bindingId),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("JavaScript semantic analysis: class expression lexical names", () => {
|
||||
it("resolves named class expressions inside the class without changing the outer binding", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
|
|
|
|||
|
|
@ -265,6 +265,12 @@ const scopeKind = (
|
|||
if (t.isFunction(node)) return "function";
|
||||
if (t.isClass(node)) return "class";
|
||||
if (t.isCatchClause(node)) return "catch";
|
||||
if (
|
||||
t.isForStatement(node) ||
|
||||
t.isForOfStatement(node) ||
|
||||
t.isForInStatement(node)
|
||||
)
|
||||
return "block";
|
||||
if (
|
||||
t.isBlockStatement(node) &&
|
||||
!(parent !== null && t.isFunction(parent) && parent.body === node)
|
||||
|
|
|
|||
Loading…
Reference in New Issue