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

useDefineWithClassFields should use-before-init error when class property initializer refers to parameter property #50971

Closed
dilame opened this issue Sep 27, 2022 · 11 comments · Fixed by #55028
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@dilame
Copy link

dilame commented Sep 27, 2022

Bug Report

This bug exists in my project, not only playground, but playground seem to also have bugs, because i can't even seem to reliably reproduce it in the playground – sometimes it does not react on Target change. But right now i have an opened playground tab with this bug.

I will duplicate the MRE code right here as a text.
This is the source code

class Helper {
    create(): boolean {
        return true
    }
}

export class Broken {

    constructor(readonly facade: Helper) {
        console.log(this.bug)
    }
    bug = this.facade.create()

}

new Broken(new Helper)

This is the compiled code with target: ES2022 and module: CommonJS

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Broken = void 0;
class Helper {
    create() {
        return true;
    }
}
class Broken {
    facade;
    constructor(facade) {
        this.facade = facade;
        console.log(this.bug);
    }
    bug = this.facade.create();
}
exports.Broken = Broken;
new Broken(new Helper);

If run this JS – we will get TypeError

Cannot read properties of undefined (reading 'create') 

⏯ Playground Link

Playground link with relevant code

I'm also sharing the playground link, but i assume it could produce the right code for you if you open it first time.
To reliable reproduce – change module to Node16 and then to CommonJS. You should see this result

Screen Shot 2022-09-27 at 19 54 16

@DanielRosenwasser
Copy link
Member

I believe that this is due to #36425

@sandersn why don't we give an error for referencing a parameter property in a field initializer when useDefineForClassFields is set?

@dilame dilame changed the title Target ES2022 + Module CommonJS silently produces code that with TypeError in runtime Target ES2022 + Module CommonJS silently produces code with TypeError in runtime Sep 27, 2022
@dilame
Copy link
Author

dilame commented Sep 27, 2022

Is target: ES2022 supposed to produce such code? If i downgrade to target: ES2021 the problem disappears

@DanielRosenwasser
Copy link
Member

In newer targets, useDefineForClassFields is set to true because it's spec-compliant. To be honest though, even I'm somewhat surprised that we changed the initialization ordering.

@fatcerberus
Copy link

even I'm somewhat surprised that we changed the initialization ordering

It’s impossible for facade to be initialized before bug in this code unless the compiler transpiles bug to a constructor-initialized property (as it does pre-ES2022).

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript Help Wanted You can do this labels Sep 27, 2022
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Sep 27, 2022
@RyanCavanaugh
Copy link
Member

The code generation here is as-expected; the bug is the lack of error message to flag the use-before-init.

@fatcerberus
Copy link

this.facade = facade;

Not directly related to the issue but if target is ES2022 (which implies useDefineForClassFields: true) then why isn't this being initialized with defineProperty? Are parameter properties exempt?

@RyanCavanaugh
Copy link
Member

Parameter properties are considered to be sugar for a this.e = e assignment in the constructor.

@dilame
Copy link
Author

dilame commented Sep 28, 2022

The code generation here is as-expected

Doesn't it reduces readability? I was so happy TypeScript handles initialization ordering in constructor for me.
Now i will be forced to rewrite all such parts in the whole project with less accurate code. At least, as i understand, it requires 2 lines of code instead of 1. One line with field type declaration, and second line with value assignment in constructor.

@rbuckton
Copy link
Member

rbuckton commented Nov 2, 2022

It looks like our expectation (prior to useDefineForClassFields), was that parameter property initializers are assigned before instance initializers, as evidenced by the emit when useDefineForClassFields is false:

    constructor(facade) {
        this.facade = facade;
        this.bug = this.facade.create();
        console.log(this.bug);
    }

I would contend that the issue is our emit when useDefineForClassFields is true isn't correctly moving the field initializers into the constructor. Really, the emit should be this:

    facade;
    bug;
    constructor(facade) {
        this.facade = facade;
        this.bug = this.facade.create();
        console.log(this.bug);
    }

@AlCalzone
Copy link
Contributor

FWIW, babel also produces code where this is a runtime error.
If this is the desired initialization order, then it should be a compiler error.

@sandersn sandersn changed the title Target ES2022 + Module CommonJS silently produces code with TypeError in runtime useDefineWithClassFields should use-before-init error when class property initialiser refers to parameter property Feb 7, 2023
@sandersn sandersn changed the title useDefineWithClassFields should use-before-init error when class property initialiser refers to parameter property useDefineWithClassFields should use-before-init error when class property initializer refers to parameter property Feb 7, 2023
eamodio added a commit to gitkraken/vscode-gitlens that referenced this issue Feb 10, 2023
@Azbesciak
Copy link

Hi, anything guys? I also faced that, to say more we have ngrx code, which declares multiple effects in a class based on received in the constructor values, but these declared properties are not declared in the constructor. That is overkill.

From other fields, like kotlin or scala - you have a constructor, and later you can use these values from the constructor, to create other properties.

That should be obvious, and IMO it was for many users before ES2022.

eamodio added a commit to gitkraken/vscode-gitlens that referenced this issue Jun 12, 2023
axosoft-ramint pushed a commit to gitkraken/vscode-gitlens that referenced this issue Jun 12, 2023
axosoft-ramint added a commit to gitkraken/vscode-gitlens that referenced this issue Jun 12, 2023
Removes additional commands from branches/branch nodes in closed repos

Adds better typing for view commands

Avoids use-before-init w/ useDefineForClassFields
See microsoft/TypeScript#50971
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Help Wanted You can do this
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants