Skip to content

Class field initialization order different between target ES2021 and ES2022 #52331

Open
@DillonJettCallis

Description

@DillonJettCallis

Bug Report

πŸ”Ž Search Terms

ES2022 class field initialization order

πŸ•— Version & Regression Information

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

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

interface Param {
  a: number;
}

class Test {
  // constructor that sets a field
  constructor(private param: Param){
  }

  // use that field to set another field
  a = this.param.a;
}

const t = new Test({a: 10});
console.log(t.a);

πŸ™ Actual behavior

If the compiler is set to target "ES2021" then the output will be:

"use strict";
class Test {
    constructor(param) {
        this.param = param;
        this.a = this.param.a;
    }
}
const t = new Test({ a: 10 });
console.log(t.a);

If the target is set to "ES2022" then the output will be:

"use strict";
class Test {
    param;
    constructor(param) {
        this.param = param;
    }
    a = this.param.a;
}
const t = new Test({ a: 10 });
console.log(t.a);

These look logically the same, just utilizing the field syntax in Javascript, but in Javascript fields are initialized BEFORE the constructor is run.

So that means that ES2021 will work, setting the param field on Test and then use it to set the a field, but with ES2022 it will attempt to set a first, using the param field which has not been initialized yet, thus throwing an error at runtime.

There is no warning or error that this will happen, and I could not find any kind of compiler flag to enable such an error. Even "strictPropertyInitialization" didn't catch this like I thought it might.

πŸ™‚ Expected behavior

In order to maintain Javascript semantics, I believe that Typescript should not allow field initializers to access these automatic field setting constructor parameters. Typescript should not allow you to do something that will simply not work.

Alternatively, the compiler could be changed to insert the initializer at the end of the constructor. This would maintain existing Typescript behavior at the expense of being different from Javascript.

Activity

whzx5byb

whzx5byb commented on Jan 20, 2023

@whzx5byb

Related: #50971

added this to the Backlog milestone on Jan 24, 2023
RyanCavanaugh

RyanCavanaugh commented on Jan 24, 2023

@RyanCavanaugh
Member

Alternatively, the compiler could be changed to insert the initializer at the end of the constructor

I don't think we can change the emit here, but giving a correct error under strictPropertyInitialization is definitely desirable here

tmtron

tmtron commented on Jun 9, 2023

@tmtron

Alternatively, the compiler could be changed to insert the initializer at the end of the constructor

I don't think we can change the emit here, but giving a correct error under strictPropertyInitialization is definitely desirable here

So I understand that we must change our code when we want to target es2022. Unfortunately we rely on the initialization order in lots of places all over the code-base.
Do you plan to provide some sort of update-script or eslint auto-fix?

added a commit that references this issue on Jun 30, 2023
kirbysayshi

kirbysayshi commented on Mar 29, 2024

@kirbysayshi

I'm not sure if this also falls under strictPropertyInitialization or not:

class API {
  private _state = this.initialState();

  constructor(private storage: Map<string, unknown>) {}

  initialState() {
    return this.storage.get("anything");
  }
}

This is arguably, to me at least, an example where the _state field is not being strictly initialized because it's effectively impossible under ES2022 (!). this.storage will not be set by the time this.initialState() is executed as a field. It's hard to know right now how many instances of code like this is lurking in a codebase!

Playground Link

5 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Class field initialization order different between target ES2021 and ES2022 Β· Issue #52331 Β· microsoft/TypeScript