Skip to content

Functions wrapped in __importDefault with allowSyntheticDefaultImports emit incorrect function calls #35420

Description

@borekb

The following code:

import allSettled from "promise.allsettled";

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

allSettled([resolved, rejected]).then(results => {
  console.log(results);
});

Is transpiled into the following JS with esModuleInterop & allowSyntheticDefaultImports:

"use strict";
var __importDefault =
  (this && this.__importDefault) ||
  function(mod) {
    return mod && mod.__esModule ? mod : { default: mod };
  };
Object.defineProperty(exports, "__esModule", { value: true });
const promise_allsettled_1 = __importDefault(require("promise.allsettled"));
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
promise_allsettled_1.default([resolved, rejected]).then(results => {
  console.log(results);
});

Full tsconfig.json:

{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "strict": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
  },
  "include": [
    "src"
  ]
}

(CodeSandbox.)

Executing this code leads to:

TypeError: #<Object> is not a constructor
    at Object.resolve (<anonymous>)
    at Object.PromiseResolve (/sandbox/node_modules/es-abstract/es2018.js:160:10)
    at /sandbox/node_modules/promise.allsettled/implementation.js:26:24
    at Function.from (<anonymous>)
    at Object.allSettled (/sandbox/node_modules/promise.allsettled/implementation.js:19:22)
    at Object.allSettled [as default] (/sandbox/node_modules/promise.allsettled/index.js:16:9)
    at Object.<anonymous> (/sandbox/src/demo.ts:6:11)
    at Module._compile (internal/modules/cjs/loader.js:776:30)
    at Module.m._compile (/sandbox/node_modules/ts-node/src/index.ts:536:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:787:10)

That is because the promise.allsettled package handles this according to the spec, see es-shims/Promise.allSettled#5. The author of that library is Jordan Harband (@ljharb) who will have deeper understanding of the specifics than I do. (Also, thanks for nudging me to report this issue!)

The problem is most likely the emitted function call, which looks like this:

promise_allsettled_1.default([resolved, rejected]).then(results => {
  console.log(results);
});

In comparison, Babel + TS emits this (which works):

(0, _promise.default)([resolved, rejected]).then(function (results) {
  console.log(results);
});

Babel playground.

TypeScript Version: 3.7.2

Search Terms: esModuleInterop, allowSyntheticDefaultImports, emit, transpile, downlevel compile, ES Modules, default, wrapper, Node.js, module.

Activity

  1. ljharb commented on Nov 29, 2019

    @ljharb
    Contributor

    A simpler repro case would be importing module.exports = function () { 'use strict'; return !!this; } (with esModuleInterop and synthetic imports enabled, of course) - a correct downleveling would make invoking this function with no implied receiver return false; TS’s output currently seems to make it return true.

  2. ajafff commented on Dec 27, 2019

    @ajafff
    Contributor

    AFAICT the same bug exists for named imports.

    // foo.ts
    export function foo() { return !!this; }
    
    // bar.ts
    import { foo } from './foo.js';
    foo();
    
    // transpiled bar.js
    var foo_js_1 = require('./foo.js');
    foo_js_1.foo(); // this line should be `(0, foo_js_1.foo)()`

    Jordan Harband (@ljharb) please correct me if I'm wrong.

  3. ljharb commented on Dec 27, 2019

    @ljharb
    Contributor

    Yes, that’s the same bug, thanks.

  4. added a commit that references this issue on Dec 27, 2019
    ec2ef0b
  5. ajafff commented on Dec 27, 2019

    @ajafff
    Contributor

    Fix is up at #35877

  6. added a commit that references this issue on Feb 9, 2021
    2d77419
  7. added a commit that references this issue on Mar 3, 2021
    7751ecb
  8. gdh1995 commented on Jul 4, 2021

    @gdh1995
    Contributor

    Ron Buckton (@rbuckton) Sorry to disturb you, but your PR seems to wrap all imported symbols with (0, ...), but I think it's not necessary and may be skipped when the target is a "const arrow function".

  9. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issueRescheduledThis issue was previously scheduled to an earlier milestone

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions