tc39/proposal-decorators

Metadata placement inconsistency with replaced classes

Closed this issue · 4 comments

Metadata on the prototype is assigned prior to replacement classes whereas static metadata is after. While this should mostly be OK due to metadata inheritance (depending on shadowing), it could cause problems for direct references to the original class, for example from those made within the class itself.

const META = Symbol('meta')
function setMeta (data) {
  return function (value, { setMetadata }) {
    setMetadata(META, data)
  }
}

function replace (C) {
  return class Replacer extends C {}
}

@replace
class C {

  @setMeta('static')
  static x = 1;
  
  @setMeta('instance')
  x = 2;
  
  reportMeta () {
    console.log(C[Symbol.metadata][META].public.x) // undefined <- Expected?
    console.log(C.prototype[Symbol.metadata][META].public.x) // "instance"
  }
}
  
console.log(C[Symbol.metadata][META].public.x) // "static" (C here is Replacer)

C in both cases would refer to Replacer, all references to the original class are rebound to the replacement classes provided by any decorators.

Has this changed since PR2417? That still has class scope class name binding prior to replacement, so reportMeta's reference to C would be the original, not Replacer.

#329 covers this to an extent, but I wasn't sure what decisions (if any) came out of that.

That was the intent of the design, if I made a mistake in the spec would definitely appreciate your help in pointing it out. I will be rereviewing and doing another round of refinement in the near future, but am currently focused on implementing the Babel plugin.

The outcome of #329 was that classes would continue to be applied before static class fields are applied. If decorators wish to run code after the class has been fully constructed and static class fields had been assigned, then they could use @init to add an initializer which would run at that time.

Closing: intended design should not exhibit this behavior. Added a comment in the PR.