You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ES decorator metadata emit throws when a decorated class extends null #65363
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:
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.
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.
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) andObject.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.
Activity
visualkhh commentedon Nov 8, 2026
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):Running the emitted module with a
Symbol.metadatapolyfill throwsTypeError: Cannot read properties of null (reading 'Symbol(metadata)'). Without the polyfill the whole expression short-circuits tovoid 0, so the crash is invisible until something installsSymbol.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 --emitDecoratorMetadataemits the old__decorate([dec], L)form and never touchesSymbol.metadata, so it loads fine. Anyone onexperimentalDecoratorsis unaffected — that includes most DI frameworks, and the one I run on (@dooboostore/simple-boot), since none of them readSymbol.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 {})isFunction.prototype, and with the polyfill installedFunction.prototype[Symbol.metadata]isundefined, soObject.create(undefined ?? null)gives thenull-prototype metadata object they describe. That means the fix is to read the parent metadata from the constructor's prototype chain rather than from_classSuperdirectly — i.e.(_classSuper ?? Function.prototype)[Symbol.metadata], or equivalently capturingObject.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.