Describe the bug
When lowering object rest for an ES2015 target, SWC transforms a var destructuring declaration in a for-of head into declarations using let inside the loop body.
The bound names of:
for (var { a, ...b } of iterable) {}
belong to the surrounding variable environment and must remain accessible after the loop.
However, moving them into let declarations inside the loop body changes their scope and lifetime.
Input code
for (var { a, ...b } of [{ x: 1, y: 2 }]) {}
console.log(a, b);
Config
{
"jsc": {
"parser": {
"syntax": "ecmascript"
},
"minify": {
"compress": false,
"mangle": false
},
"target": "es2015"
},
"minify": false
}
Link to the code that reproduces this issue
https://play.swc.rs/?version=1.16.1&code=H4sIAAAAAAAAA0vLL1LQKEssUqhWSNRR0NPTS1KoVchPU4iuVqiwUjDUUai0UjBSqI3VVKiu5UrOzyvOz0nVy8lP1wCqTtK0BgCoRhSNPwAAAA%3D%3D&config=H4sIAAAAAAAAA1WNvQqAMBCDd5%2BidHZQwcW3OcopFVvLXQdL8d09K%2F5tyReS5EopPbPRg8oixQQgRnq8EE4%2BwiZEo3HAhmyIuoR7fXWc9XZM345ZXSBkFjbCwljfgQM%2FLXjj30oEmjCWH%2B6atj8%2FSvbuX6X9AERNOxe2AAAA
SWC Info output
No response
Expected behavior
Both a and b are declared using var, so they should remain accessible after the loop.
Expected output:
undefined {x:1,y:2}
Using var declaration inside the loop body would also preserve the required variable scope:
...
for (var _ref of [
{
x: 1,
y: 2
}
]){
var { a } = _ref, b = _object_without_properties(_ref, [
"a"
]);
}
console.log(a, b);
Actual behavior
function _object_without_properties(source, excluded) {
if (source == null) return {};
var target = {}, sourceKeys, key, i;
if (typeof Reflect !== "undefined" && Reflect.ownKeys) {
sourceKeys = Reflect.ownKeys(Object(source));
for(i = 0; i < sourceKeys.length; i++){
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
return target;
}
target = _object_without_properties_loose(source, excluded);
if (Object.getOwnPropertySymbols) {
sourceKeys = Object.getOwnPropertySymbols(source);
for(i = 0; i < sourceKeys.length; i++){
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _object_without_properties_loose(source, excluded) {
if (source == null) return {};
var target = {}, sourceKeys = Object.getOwnPropertyNames(source), key, i;
for(i = 0; i < sourceKeys.length; i++){
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
return target;
}
for (var _ref of [
{
x: 1,
y: 2
}
]){
let { a } = _ref, b = _object_without_properties(_ref, [
"a"
]);
}
console.log(a, b); // reference error
Version
1.16.1
Additional context
The closest previous fix appears to be #7437. That change used let for declarations generated by the object-rest transform in order to preserve per-iteration lexical bindings. However, the transform currently appears to use VarDeclKind::Let unconditionally.
The transform likely needs to distinguish the original declaration kind:
- An original
var declaration must preserve function/global variable scope.
- Original
let and const declarations must preserve per-iteration lexical bindings.
Simply changing all generated declarations back to var would likely regress #6988, so the transformation should branch based on the declaration kind or hoist var bindings and emit assignments inside each iteration.
Describe the bug
When lowering object rest for an ES2015 target, SWC transforms a
vardestructuring declaration in afor-ofhead into declarations usingletinside the loop body.The bound names of:
belong to the surrounding variable environment and must remain accessible after the loop.
However, moving them into let declarations inside the loop body changes their scope and lifetime.
Input code
Config
{ "jsc": { "parser": { "syntax": "ecmascript" }, "minify": { "compress": false, "mangle": false }, "target": "es2015" }, "minify": false }Link to the code that reproduces this issue
https://play.swc.rs/?version=1.16.1&code=H4sIAAAAAAAAA0vLL1LQKEssUqhWSNRR0NPTS1KoVchPU4iuVqiwUjDUUai0UjBSqI3VVKiu5UrOzyvOz0nVy8lP1wCqTtK0BgCoRhSNPwAAAA%3D%3D&config=H4sIAAAAAAAAA1WNvQqAMBCDd5%2BidHZQwcW3OcopFVvLXQdL8d09K%2F5tyReS5EopPbPRg8oixQQgRnq8EE4%2BwiZEo3HAhmyIuoR7fXWc9XZM345ZXSBkFjbCwljfgQM%2FLXjj30oEmjCWH%2B6atj8%2FSvbuX6X9AERNOxe2AAAA
SWC Info output
No response
Expected behavior
Both
aandbare declared usingvar, so they should remain accessible after the loop.Expected output:
undefined {x:1,y:2}Using var declaration inside the loop body would also preserve the required variable scope:
Actual behavior
Version
1.16.1
Additional context
The closest previous fix appears to be #7437. That change used
letfor declarations generated by the object-rest transform in order to preserve per-iteration lexical bindings. However, the transform currently appears to useVarDeclKind::Letunconditionally.The transform likely needs to distinguish the original declaration kind:
vardeclaration must preserve function/global variable scope.letandconstdeclarations must preserve per-iteration lexical bindings.Simply changing all generated declarations back to var would likely regress #6988, so the transformation should branch based on the declaration kind or hoist var bindings and emit assignments inside each iteration.