Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async generator for-await loop with no yield incorrectly yields iterator node when targeting es6 or lower #8805

Closed
roxchkplusony opened this issue Apr 2, 2024 · 2 comments · Fixed by #8881
Assignees
Labels
Milestone

Comments

@roxchkplusony
Copy link

Describe the bug

I have an async generator which has a for-await and in a base case does not yield anything. This causes a yield of an iterator next object rather than allowing the client to iterate nothing.

Input code

async function* foo(): AsyncGenerator<number> {
  yield 1
}

async function* bar(inputs: AsyncGenerator<number>, returnValues: boolean) {
  for await (const input of inputs) {
    if (!returnValues) {
      return
    }
    yield input
  }
}

async function run() {
  for await (const number of bar(foo(), true)) {
    console.log(number)
  }

  for await (const value of bar(foo(), false)) {
    console.log(value)
  }
}

run()

Config

I transpiled this TS file into a JS file and ran the JS file with Node. I also tried the same thing using tsc to compare the results.

package.json:

{
  "name": "repro",
  "devDependencies": {
    "@swc/cli": "^0.3.12",
    "@swc/core": "^1.4.11",
    "typescript": "^5.4.3"
  }
}

Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.4.11&code=H4sIAAAAAAAAA3WQwUpEMQxF9%2F2K666VQXArIrjyD9znPVsp1ETSdmSQ%2BXenqQ9knNmUNr09OSnVA69IndeWhW%2BRRHx4wPMov0SOSk30kfvHEvUJ3w445FjecO%2BOztHZ44XUZ%2F7srV4j7KCxdeVXKj2eUotIicTByEkU9EW5wa%2FCtcFYkDQ3daaAnOBv%2FnK2C%2FzS7XC0ddraezdq%2F6yhnf2V%2FlN6CIzJ7Gt2aNpj2DqO3GmCuyLvfqaDtblE2w%2FXM1iiUi%2FTLB02Z5P8AdfQKzmsAQAA&config=H4sIAAAAAAAAA1VPSQ7CMAy85xWRzxwACQ78gUdEwa2Csil2JaKqfydpk0Jv9iye8SykhDdpeMi5jGWJKhGmfS8IZc%2FqUxDgHJF0MpHh1FmmSg3KEq7QsjHAKo3I1YV0PV9uzQE2BMLuaJgz3gz5P1MHFxMSHYVVqvxo8ZgoWiq48JpWsv1S%2B24N7vAT9bD9MBh6dienCcXyBbnuEC8XAQAA

SWC Info output

Operating System:
    Platform: darwin
    Arch: arm64
    Machine Type: arm64
    Version: Darwin Kernel Version 23.2.0: Wed Nov 15 21:59:33 PST 2023; root:xnu-10002.61.3~2/RELEASE_ARM64_T8112
    CPU: (8 cores)
        Models: Apple M2

Binaries:
    Node: 18.19.1
    npm: 10.2.4
    Yarn: N/A
    pnpm: 8.14.1

Relevant Packages:
    @swc/core: 1.4.11
    @swc/helpers: N/A
    @swc/types: 0.1.6
    typescript: 5.4.3

SWC Config:
    output: N/A
    .swcrc path: N/A

Next.js info:
    output: N/A

Expected behavior

Output of running node foo.js:

1

Actual behavior

Output of running node foo.js:

1
{ value: undefined, done: true }

Version

1.4.11

Additional context

No response

@roxchkplusony
Copy link
Author

SWC transpiled output is in the playground link. The TSC transpiled output is below:

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
    var g = generator.apply(thisArg, _arguments || []), i, q = [];
    return i = {}, verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
    function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
    function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
    function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
    function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
    function fulfill(value) { resume("next", value); }
    function reject(value) { resume("throw", value); }
    function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
    var m = o[Symbol.asyncIterator], i;
    return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
    function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
    function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
function foo() {
    return __asyncGenerator(this, arguments, function foo_1() {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, __await(1)];
                case 1: return [4 /*yield*/, _a.sent()];
                case 2:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function bar(inputs, returnValues) {
    return __asyncGenerator(this, arguments, function bar_1() {
        var _a, inputs_1, inputs_1_1, input, e_1_1;
        var _b, e_1, _c, _d;
        return __generator(this, function (_e) {
            switch (_e.label) {
                case 0:
                    _e.trys.push([0, 9, 10, 15]);
                    _a = true, inputs_1 = __asyncValues(inputs);
                    _e.label = 1;
                case 1: return [4 /*yield*/, __await(inputs_1.next())];
                case 2:
                    if (!(inputs_1_1 = _e.sent(), _b = inputs_1_1.done, !_b)) return [3 /*break*/, 8];
                    _d = inputs_1_1.value;
                    _a = false;
                    input = _d;
                    if (!!returnValues) return [3 /*break*/, 4];
                    return [4 /*yield*/, __await(void 0)];
                case 3: return [2 /*return*/, _e.sent()];
                case 4: return [4 /*yield*/, __await(input)];
                case 5: return [4 /*yield*/, _e.sent()];
                case 6:
                    _e.sent();
                    _e.label = 7;
                case 7:
                    _a = true;
                    return [3 /*break*/, 1];
                case 8: return [3 /*break*/, 15];
                case 9:
                    e_1_1 = _e.sent();
                    e_1 = { error: e_1_1 };
                    return [3 /*break*/, 15];
                case 10:
                    _e.trys.push([10, , 13, 14]);
                    if (!(!_a && !_b && (_c = inputs_1.return))) return [3 /*break*/, 12];
                    return [4 /*yield*/, __await(_c.call(inputs_1))];
                case 11:
                    _e.sent();
                    _e.label = 12;
                case 12: return [3 /*break*/, 14];
                case 13:
                    if (e_1) throw e_1.error;
                    return [7 /*endfinally*/];
                case 14: return [7 /*endfinally*/];
                case 15: return [2 /*return*/];
            }
        });
    });
}
function run() {
    return __awaiter(this, void 0, void 0, function () {
        var _a, _b, _c, number, e_2_1, _d, _e, _f, value, e_3_1;
        var _g, e_2, _h, _j, _k, e_3, _l, _m;
        return __generator(this, function (_o) {
            switch (_o.label) {
                case 0:
                    _o.trys.push([0, 5, 6, 11]);
                    _a = true, _b = __asyncValues(bar(foo(), true));
                    _o.label = 1;
                case 1: return [4 /*yield*/, _b.next()];
                case 2:
                    if (!(_c = _o.sent(), _g = _c.done, !_g)) return [3 /*break*/, 4];
                    _j = _c.value;
                    _a = false;
                    number = _j;
                    console.log(number);
                    _o.label = 3;
                case 3:
                    _a = true;
                    return [3 /*break*/, 1];
                case 4: return [3 /*break*/, 11];
                case 5:
                    e_2_1 = _o.sent();
                    e_2 = { error: e_2_1 };
                    return [3 /*break*/, 11];
                case 6:
                    _o.trys.push([6, , 9, 10]);
                    if (!(!_a && !_g && (_h = _b.return))) return [3 /*break*/, 8];
                    return [4 /*yield*/, _h.call(_b)];
                case 7:
                    _o.sent();
                    _o.label = 8;
                case 8: return [3 /*break*/, 10];
                case 9:
                    if (e_2) throw e_2.error;
                    return [7 /*endfinally*/];
                case 10: return [7 /*endfinally*/];
                case 11:
                    _o.trys.push([11, 16, 17, 22]);
                    _d = true, _e = __asyncValues(bar(foo(), false));
                    _o.label = 12;
                case 12: return [4 /*yield*/, _e.next()];
                case 13:
                    if (!(_f = _o.sent(), _k = _f.done, !_k)) return [3 /*break*/, 15];
                    _m = _f.value;
                    _d = false;
                    value = _m;
                    console.log(value);
                    _o.label = 14;
                case 14:
                    _d = true;
                    return [3 /*break*/, 12];
                case 15: return [3 /*break*/, 22];
                case 16:
                    e_3_1 = _o.sent();
                    e_3 = { error: e_3_1 };
                    return [3 /*break*/, 22];
                case 17:
                    _o.trys.push([17, , 20, 21]);
                    if (!(!_d && !_k && (_l = _e.return))) return [3 /*break*/, 19];
                    return [4 /*yield*/, _l.call(_e)];
                case 18:
                    _o.sent();
                    _o.label = 19;
                case 19: return [3 /*break*/, 21];
                case 20:
                    if (e_3) throw e_3.error;
                    return [7 /*endfinally*/];
                case 21: return [7 /*endfinally*/];
                case 22: return [2 /*return*/];
            }
        });
    });
}
run();

@kdy1 kdy1 self-assigned this Apr 18, 2024
@kdy1 kdy1 added this to the Planned milestone Apr 18, 2024
kdy1 added a commit that referenced this issue Apr 23, 2024
@kdy1 kdy1 modified the milestones: Planned, v1.4.17 Apr 23, 2024
@swc-bot
Copy link
Collaborator

swc-bot commented May 23, 2024

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@swc-project swc-project locked as resolved and limited conversation to collaborators May 23, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Development

Successfully merging a pull request may close this issue.

3 participants