Skip to content

ES decorator metadata emit throws when a decorated class extends null #65363

Open

Description

🔎 Search Terms

decorator metadata extends null Symbol.metadata

🕗 Version & Regression Information

  • main at 6aa91ad (native compiler built from source)

💻 Code

// @target: es2022
function dec(_: any, context: ClassDecoratorContext) {}
@dec
export class C extends null {}

🙁 Actual behavior

A class using standard (non-legacy) decorators that extends null crashes at load when targeting below ESNext. The emit reads _classSuper[Symbol.metadata] with _classSuper = null:

let _classSuper = null;
// ...
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;

which throws TypeError: Cannot read properties of null (reading 'Symbol(Symbol.metadata)') (observed in Node with a Symbol.metadata polyfill).

🙂 Expected behavior

No error, and C[Symbol.metadata] has a null prototype. The decorator metadata proposal reads the parent metadata from the constructor parent, which for extends null is Function.prototype, whose Symbol.metadata is null.

Activity

  1. visualkhh commented on Nov 8, 2026

    @visualkhh

    Reproduced on released 5.9.3, not just main — and confirmed the bug is exclusive to the standard-decorator path, which narrows the blast radius usefully.

    Repro (tsc 5.9.3, --target es2022 --module esnext):

    let _classSuper = null;
    var C = class extends _classSuper {
        // ...
        const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;

    Running the emitted module with a Symbol.metadata polyfill throws TypeError: Cannot read properties of null (reading 'Symbol(metadata)'). Without the polyfill the whole expression short-circuits to void 0, so the crash is invisible until something installs Symbol.metadata — which every metadata-aware decorator library does, so it surfaces exactly where decorators are actually used.

    The legacy path is clean. Same class compiled with --experimentalDecorators --emitDecoratorMetadata emits the old __decorate([dec], L) form and never touches Symbol.metadata, so it loads fine. Anyone on experimentalDecorators is unaffected — that includes most DI frameworks, and the one I run on (@dooboostore/simple-boot), since none of them read Symbol.metadata; the failure is confined to standard decorators.

    On the expected value: the reporter's reading checks out at runtime — Object.getPrototypeOf(class extends null {}) is Function.prototype, and with the polyfill installed Function.prototype[Symbol.metadata] is undefined, so Object.create(undefined ?? null) gives the null-prototype metadata object they describe. That means the fix is to read the parent metadata from the constructor's prototype chain rather than from _classSuper directly — i.e. (_classSuper ?? Function.prototype)[Symbol.metadata], or equivalently capturing Object.getPrototypeOf(ctor).

    One test-shape suggestion since this is easy to regress: assert both halves in one fixture — the module loads (no throw) and Object.getPrototypeOf(C[Symbol.metadata]) === null. A load-only assertion passes if someone "fixes" it by skipping metadata entirely, which would silently drop inherited metadata for the normal (non-extends null) case.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions