Skip to content

In target: ES5, Base class constructor's primitive return value should be ignored #51738

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

Open
Josh-Cena opened this issue Dec 3, 2022 · 6 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@Josh-Cena
Copy link
Contributor

Bug Report

πŸ”Ž Search Terms

class, extends, constructor, return primitive

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about class constructors

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

new class extends class {
  constructor() {
    return 1;
  }
} {
  constructor() {
    super();
    console.log(typeof this);
  }
}

πŸ™ Actual behavior

Logs "number"

πŸ™‚ Expected behavior

Logs "object"

@MartinJohns
Copy link
Contributor

MartinJohns commented Dec 3, 2022

This is working as intended. It's legit JavaScript. For TypeScript to change this behaviour would violate the language design goals.

Related: #38519 / #27594 / #13819

@Josh-Cena
Copy link
Contributor Author

Josh-Cena commented Dec 3, 2022

@MartinJohns The change can be trivially done in the downlevelingβ€”it requires no type information.

new /** @class */ (function (_super) {
    __extends(class_1, _super);
    function class_1() {
-       var _this = _super.call(this) || this;
+       var _this = _super.call(this);
+       if (_this === null || (typeof _this !== "object" && typeof _this !== "function"))
+           _this = this;
        console.log(typeof _this);
        return _this;
    }
    return class_1;
}(/** @class */ (function () {
    function class_2() {
        return 1;
    }
    return class_2;
}())));

FWIW, anything that Babel does right but tsc does wrong looks like a bug to me.

@MartinJohns
Copy link
Contributor

Nevermind my earlier comment. I wrongly assumed the JavaScript will behave the same.

@fatcerberus
Copy link

You probably should have specified that this only happens with ES5-downleveled code. From the OP it seemed like you meant this happened regardless of target (which would indicate it was just normal JS behavior).

@Josh-Cena Josh-Cena changed the title Base class constructor's primitive return value should be ignored ES5 output: Base class constructor's primitive return value should be ignored Dec 3, 2022
@Josh-Cena Josh-Cena changed the title ES5 output: Base class constructor's primitive return value should be ignored In target: ES5, Base class constructor's primitive return value should be ignored Dec 3, 2022
@RyanCavanaugh
Copy link
Member

FWIW, anything that Babel does right but tsc does wrong looks like a bug to me.

You should use Babel in that case. The TS emit takes some complexity/compliance tradeoffs and is much less configurable.

I don't think we want to risk breaking any ES5 programs at this point given how old of a target it is.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Dec 5, 2022
@Josh-Cena
Copy link
Contributor Author

I can guess the historical context here (tsc implementing classes before the latter getting standardized), but IMO the current behavior doesn't benefit anyone and is unlikely to be relied on. If this is a primitive, it means you can't mutate it whatsoever. Unless you do something really weird like:

function SomethingThatReturnsEitherObjectOrNumberIDK() {
  if (somethingNotGoingWell) return 1;
  return {
    the: "actual object that I want to construct",
  };
}

declare class SomethingThatReturnsEitherObjectOrNumberIDK {
  the?: string;
}

new class extends SomethingThatReturnsEitherObjectOrNumberIDK {
  constructor() {
    super();
    if (typeof this === "number")
      return { the: `number wrapped into an object: ${this}` }; // type: { the: never }
  }
}

But that doesn't even get correct types! Why would someone use tsc, choose its bizarre subclassing behavior, but also disregard the types?

Built-in subclassing not working is a missed but understandable tradeoff, but this one seems niche and not as breaking-change-prone, while increasing tsc's conformance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants