microsoft / microsoft/TypeScript

Proposal: Add new __construct helper for better ES5/ES6 class interop

Open
#15,397 3 comments 26 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Awaiting More Feedback Suggestion
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

I propose we add a new helper to assist with class instance construction runtime semantics when extending ES6 built-in classes while compiling with --target ES5.

Background

Our current emit for classes for --target ES5 assumes that the superclass follows the same runtime semantics as the classes we emit. Generally this means that the constructor can be called as a function via call() or apply(). However, a number of ES6 built-in classes are specified to throw when not used as a constructor (i.e. Promise, Map, etc.), and other ES6 built-in classes return a value when called, ignoring the this value provided to call() or apply() (i.e. Error, Array, etc.).

Previously we provided guidance for possible workarounds for this to support the latter scenario, but we do not currently have a solution for the former scenario.

Proposal

The following code listing describes a new __construct helper that we would need to emit for any file that contains an explicit (or implicit, for property declarations) super() call:

class MyPromise extends Promise {
  constructor(executor) {
    super(executor);
  }
}

// becomes...
var __extends = ...;
var __construct = (this && this.__construct) || (typeof Reflect !== "undefined" && Reflect.construct
    ? function (s, t, a, n) { return t !== null ? Reflect.construct(t, a, n) : s; }
    : function (s, t, a) { return t !== null && t.apply(s, a) || s; });

var MyPromise = (function (_super) {
  __extends(MyPromise, _super);
  function MyPromise(executor) {
    var _this = this;
    var _newTarget = this.constructor;
    _this = __construct(this, _super, [executor], _newTarget);
    return _this;
  }
  return MyPromise;
})(Promise);
Benefits
  • Allows down-level class emit to extend ES6 built-ins if running in an ES6 host by feature detecting Reflect.construct.
  • Falls back to the existing behavior when running in an ES5 host.
  • Handles extends null and extends x when x is null in the same way as existing behavior.
  • Handles custom return values from super in the same way as existing behavior.
Drawbacks
  • Larger helper footprint
  • Subclassing a built-in in an ES5 host has different runtime semantics than subclassing a built-in in an ES6 host:
    • In ES5, subclassing Array or Error will not have the correct prototype chain. The only solution is to explicitly set the prototype chain using the non-standard __proto__ property as per established guidance.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue proposes a new __construct helper for ES5 class emission when extending ES6 built-ins. Start by locating the existing __extends helper and down-level class emission; the proposal defines the Reflect.construct feature detection and fallback behavior. Done means supporting the described ES6-host and ES5-host semantics, including extends null and custom super return values.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.