swc-project / swc

Rust-based platform for the Web
https://swc.rs
Apache License 2.0
31.14k stars 1.23k forks source link

[Bug] 2022-03 decorator initialise class instance in wrong order #9669

Open jzhan-canva opened 4 days ago

jzhan-canva commented 4 days ago

Describe the bug

As per tc39/proposal-decorators [Adding initialization logic with addInitializer] Method and Getter/Setter decorators should be initialised before any class fields are initialised

Input code

const myDecorator = (value, context) => {
  context.addInitializer(() => {
    console.log("init")
  })
  return value
}

class A {
  value = this.getNumber();
  value2 = this.getNumber();

  @myDecorator
  getNumber() {
    console.log("getNumber")
    return 1
  }
}

const a = new A()

Config

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true,
      "tsx": true
    },
    "transform": {
      "decoratorVersion": "2022-03"
    },
    "target": "es2021",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}

Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.7.39&code=H4sIAAAAAAAAA3WQzwrCMAzG732KsFMLMtCrTBx48eI71C7MQtdCm%2FmXvbtdYGUHhRBIvi%2FJj5jgE8HwOqEJUVOI0IC8azfiBkzwhE9S0BzgI2Cpa911Z2%2FJamffGKUsBrak4LB2oZeVzaZK5f40p4g0Rg%2B8W0xCGKdTgpYHuZkv082muke6jMM1b1b7Rdv9EXMcV%2FC5XBl%2BMRWZwQrVdqZkKv6Hzuc8PqCV6gsyc1fOIQEAAA%3D%3D&config=H4sIAAAAAAAAA1VQQQ6DMAy78wqU8yYxJu2wP%2By6ewUBdaJNlRRpCPH3tVA6uCW2E1uei7KEjzTwLOcwhsUpFuS8B0Qm69U3IOAnh9Kwdh4uO9tiQ6w8sQSF5xEz4%2BWboBVZNgI8KysdsTl65C9vZNFko1td1fW1usP5WnGPPtIoQXBLQWAgEgxwpwZJEcBoq7vpaNOQcYwiZ2GUKtsP%2BX5zLJIrGGrHlUwVxRq2BA%2F4i3az%2FBi0vPbLtYXlByFGoz9uAQAA

SWC Info output

No response

Expected behavior

When I run output code, I should see console log

init
getNumber
getNumber

This is tsc playground link, and if I run the output code, it's as expected image

Actual behavior

Wehn I run output code, I see console log

getNumber
getNumber
init

image

Version

1.7.39

Additional context

No response

magic-akari commented 2 days ago

Investigating

The current implementation of the constructor is as follows:

constructor() {
    _define_property(this, "value", this.getNumber());
    _define_property(this, "value2", this.getNumber());
    _initProto(this);
}

Problem: The order of operations is incorrect. The _initProto function should be called at the top of the constructor to ensure proper prototype initialization before any instance properties are assigned.

Our inject_after_super functionality is working as intended. However, the decorator pass executes first, leading to the following transformation:

constructor() {
    _initProto(this);
}
value = this.getNumber();
value2 = this.getNumber();

And during the ES2022 pass, class fields are inserted into the constructor before the _initProto call, resulting in the undesired behavior.

magic-akari commented 1 day ago

Possible Solution

Insert initProto in the class field instead.

class A {
  #_ = _initProto(this);
  value = this.getNumber();
  value2 = this.getNumber();
}