Skip to content

repl: pass the root tsconfig's decorator options to the parser - #46457

Draft
robobun wants to merge 2 commits into
robobun/64f2d4c5/stack-basefrom
robobun/64f2d4c5/repl-tsconfig-decorators
Draft

robobun wants to merge 2 commits into
robobun/64f2d4c5/stack-basefrom
robobun/64f2d4c5/repl-tsconfig-decorators

Conversation

@robobun

@robobun robobun commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #45915 and #45557. No user reported this.

Problem

  • With no legacy decorator option in tsconfig.json, class E { accessor a = 1 } in bun repl gives SyntaxError: Unexpected identifier 'a'. Expected a ';' following a class field. Decorators get the legacy arguments. A file runs both.
  • Repl::transform_for_repl (src/runtime/cli/repl.rs:2149) passed constants, not the root tsconfig.json values that the module loader passes.

Fix

Background

Downsides

  • Changed at the prompt without those options: a decorator gets (value, context), and a parameter decorator is not called (js_parser: reject parameter decorators when experimental decorators are off #43321). experimentalDecorators in tsconfig.json restores the legacy ones.
  • Cost (release builds): +3 instructions per input, +1 per class declaration in any file, 0 allocations, 0 syscalls, .text +512 bytes.
  • Unlike a file: .load x.js is parsed as TSX (Notes).
Notes

The stack

What changes at the prompt

An input with no decorator and no accessor field does not change: the text that the REPL evaluates is byte-identical for 50 such inputs in 3 tsconfig states.

Root tsconfig.json of the working directory 1.4.3 This PR A .ts file, 1.4.3 and this PR
none, or neither option legacy, accessor is a SyntaxError TC39 TC39
experimentalDecorators legacy legacy legacy
emitDecoratorMetadata legacy, no metadata legacy, with metadata legacy, with metadata
sets the option and extends another file (#30477) legacy TC39 TC39
is in a directory above (#28605) legacy TC39 TC39

Why the default changes without a flag

Not in this PR

Measurements

Release builds (ThinLTO) of the base and of this diff on main 0793250a5b9, in one build directory. The source diff has not changed since. Tools: llvm-size, llvm-nm -S, qemu-x86_64 -one-insn-per-tb -d exec,nochain -dfilter (instructions inside one function), qemu-x86_64 -strace, gdb 16.3 (breakpoints on 16 mi_* entry points, and a dump at Bun__REPL__evaluate). perf, valgrind, strace and bloaty are not installed.

  • .text 81,989,093 to 81,989,605 bytes (+512). .rodata, .data and .bss do not change. The stripped binary is 107,583,200 bytes in both.
  • 5 of 106,985 function symbols differ: Repl::transform_for_repl 3,214 to 3,230 bytes, s_class 1,205 to 1,239 (two parser instantiations), and the new cold repl_report_class_declaration, 255 bytes (two instantiations). Options::init, scanImports, parse_worker::run_from_thread_pool and Transpiler::parse are byte-identical.
  • bun repl, per input: 322 to 325 instructions in transform_for_repl, for 2 inputs in 3 tsconfig states. Options::init: 75 to 75.
  • bun run, per class declaration, in s_class: 75 to 76 instructions (a class with no decorator, with a member decorator, with an accessor field, in .ts and .js). 84 to 83 for a class with a class decorator.
  • Allocator calls inside transform_for_repl: 2,366 to 2,366 for a session of 50 inputs without decorators. Syscalls of that session: 705 to 705, and 709 to 709 with a tsconfig.json (1 open of it in both).
  • An input with a method decorator, where no option is set: 95 to 139 allocator calls and +2.6 syscalls (clock_gettime). An input with an accessor field, which was a SyntaxError: 38 to 124 allocator calls. At a terminal it costs +18 syscalls (7 ioctl, 2 rt_sigaction), because it is awaited like every input that imports a helper.
  • Runtime transpiler cache: 0 of 12 entries differ (diff -r over js, jsx, ts and tsx modules with decorators, under no option, experimentalDecorators, and both).
  • Bun.Transpiler({ replMode: true }) output for 67 inputs: 6 differ with TC39 decorators and 10 with the legacy ones, by 0 to 8 bytes. All are top-level class declarations with decorators.

Tests

  • repl.test.ts: accessor and TC39 decorators with no tsconfig.json, two lowered classes in one session (two tests), a decorator imported from a module, .load, .copy, -p, and five directory shapes where the prompt and bun program.ts run the same program and must agree.
  • repl-transform.test.ts: a class declaration reports the class with TC39 and with legacy decorators, and only in replMode at the top level. With repl_mode deleted from the guard on a scratch build, both tests fail.
  • On a debug build of the base source: the new REPL tests and the 2 new replMode tests (4 with both transform methods) fail, and the other tests of the two files pass.
  • On a debug build of this PR: repl.test.ts 148 pass, repl-transform.test.ts 70 pass, transpiler.test.js 307, es-decorators.test.ts 427, es-decorators-esbuild.test.ts 146, decorators.test.ts 25, decorator-metadata.test.ts 5, runtime-transpiler.test.ts 15, test/regression/issue/44915.test.ts, test/cli/test/coverage.test.ts 43. test/internal/source-lints passes with the release binary.
  • Windows was not run locally. The change has no platform branch.

Self-review

26 concerns raised, 23 addressed. The two largest: the echo of a class declaration covered only TC39 decorators and moved to the statement visitor, and the metadata option waited for #46270. Not taken:

@robobun

robobun commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:04 AM PT - Oct 24th, 2026

✅ @robobun, your commit 0d7671138aa735f915238432e4556fca22954e7d passed in Build #131697! 🎉


🧪   To try this PR locally:

bunx bun-pr 46457

That installs a local version of the PR into your bun-46457 executable, so you can run:

bun-46457 --bun

@robobun

robobun commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Status

@robobun
robobun force-pushed the robobun/64f2d4c5/stack-base branch from 6e0539d to 6015b6e Compare October 24, 2026 05:42
@robobun
robobun force-pushed the robobun/64f2d4c5/repl-tsconfig-decorators branch from ad347d4 to 679eb75 Compare October 24, 2026 05:42
Comment thread src/js_parser/repl_transforms.rs Outdated
The lowering of a decorator adds statements after the class, so a REPL
input that declares such a class reported what the last of them returns:
`undefined` for a TC39 class decorator, and a property descriptor or
`undefined` for a legacy member decorator. A class declaration without
decorators reports the class.

In replMode, a class declaration at the top level of the input is now
followed by a read of its name when the lowering added statements.
…g directory

`bun repl` passed constants for the decorator options: the legacy
TypeScript decorators and no decorator metadata, whatever the
tsconfig.json said. An `accessor` field was a SyntaxError and a TC39
decorator got the legacy arguments.

The REPL now passes `experimentalDecorators` and `emitDecoratorMetadata`
of the root tsconfig.json, which the module loader passes for a file.
@robobun
robobun force-pushed the robobun/64f2d4c5/repl-tsconfig-decorators branch from 679eb75 to 0d76711 Compare October 24, 2026 05:43

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant