Skip to content

parser: fix "Scope mismatch while visiting" panic from macro tagged templates#31693

Merged
Jarred-Sumner merged 1 commit into
mainfrom
farm/9e30c2bd/fix-template-macro-scope-mismatch
Jun 2, 2026
Merged

parser: fix "Scope mismatch while visiting" panic from macro tagged templates#31693
Jarred-Sumner merged 1 commit into
mainfrom
farm/9e30c2bd/fix-template-macro-scope-mismatch

Conversation

@robobun

@robobun robobun commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Repro

// macro.ts
export function mac(...args: any[]) { return "x"; }

// index.ts
import { mac } from './macro.ts' with { type: 'macro' };
mac`a${() => { let q = 1; }}b`;
function g() { { let y = 1; } }
$ bun index.ts
panic: Scope mismatch while visiting

Any tagged-template macro invocation whose interpolations contain a scope-creating expression (arrow, function, class) panics instead of reporting the intended template literal macro invocations are not supported error. The dead-code variant (false && mac…`` ), the macros-disabled path, and the node_modules path crash the same way. Panics on release builds (kind mismatch) and debug builds (loc mismatch); same structure existed in the Zig-era visitExpr.zig`, so this predates the Rust port. Crash signature matches Sentry BUN-3BYK (`Scope mismatch while visiting`, also seen in Zig-era releases).

Cause

The parser records every scope pushed during the parse pass in scopes_in_order; the visit pass replays them in the same order and panics on divergence. In e_template (src/js_parser/visit/visit_expr.rs), when the tag resolves to a macro ref, every dispatch outcome returns before the for part in e_.parts_mut() visit loop:

  • is_control_flow_dead → replaced with undefined
  • no_macros → error + undefined
  • node_modules → error + undefined
  • macro_context.call(...) failure → plain return (and template invocations currently always fail with template literal macro invocations are not supported, src/js_parser_jsc/Macro.rs)

The scopes recorded for arrows/functions inside the interpolations are never consumed, so the next scope the visit pass pushes reads a stale entry and trips the Scope mismatch while visiting check. Same bug family as #31231 / #31340 / #31533 (constructs dropped without consuming/discarding their recorded scopes).

Fix

Visit the template parts right after visiting the tag, before the macro dispatch — the same ordering e_call uses (arguments are visited before its macro handling). All dispatch paths may then freely replace the expression: the parts' scope entries have already been consumed. The fall-through case no longer re-visits parts.

Also syncs Cargo.lock with bun_bin's manifest (bstr was added to Cargo.toml in 90f334a without the lock update; any local cargo invocation regenerates this line).

Verification

  • 3 new tests in test/bundler/transpiler/scope-mismatch-panic.test.ts (live tag, dead-flow, namespace-member tag). All three panic without the fix and pass with it.
  • test/bundler/transpiler/{scope-mismatch-panic,macro-test,transpiler,template-literal}.test.ts and test/bundler/bundler_string.test.ts all pass.

Note: #30545 (tagged-template macro support, feature) inserts a parts visit before the macro call, which would cover the live path but not the dead-flow / macros-disabled / node_modules returns. This fix is independent and minimal; #30545 rebases on top by dropping its duplicate visit loop (its fold-flag wrapper can stay).

…emplates

When a tagged template's tag resolves to a macro import, every macro
dispatch path in e_template (dead control flow, macros disabled,
node_modules, macro call failure) replaced the expression and returned
before visiting the template parts. Scopes recorded during the parse
pass for arrows/functions inside the interpolations were then never
consumed by the visit pass, panicking with "Scope mismatch while
visiting" on the next scope push.

Visit the parts right after the tag, before the macro dispatch, the
same way e_call visits its arguments before macro handling.

Also syncs Cargo.lock with bun_bin's manifest (bstr was added to
Cargo.toml without the lock update).
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 31 minutes and 32 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2b272985-1c9b-4473-b709-182f2ba1ed3c

📥 Commits

Reviewing files that changed from the base of the PR and between 8cf3737 and c4473c3.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • src/js_parser/visit/visit_expr.rs
  • test/bundler/transpiler/scope-mismatch-panic.test.ts

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the claude label Jun 2, 2026
@robobun

robobun commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 2:05 AM PT - Jun 2nd, 2026

@robobun, your commit c4473c3 has 7 failures in Build #59787 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 31693

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

bun-31693 --bun

@robobun

robobun commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator Author

Status

Reproduced on current main (debug) and release 1.4.0 with:

import { mac } from './macro.ts' with { type: 'macro' };
mac`a${() => { let q = 1; }}b`;   // panic: Scope mismatch while visiting

plus the dead-flow (false && mac `) and namespace-tag (`macros.mac`… `) variants.

Cause: e_template's macro dispatch returns before visiting the template parts on all four outcome paths, leaving the interpolations' parse-pass scope entries unconsumed — the visit pass desyncs and panics on the next scope push.

Fix: visit parts before the macro dispatch (mirrors e_call). The three regression tests fail (panic) without the fix and pass with it; macro/transpiler/template-literal/bundler_string suites are green locally.

Waiting on CI.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. crash: support template literals in macros #18047 - Reports a panic (TODO: support template literals in macros) when using tagged template literals with macro imports, which is the exact crash this PR resolves by visiting template parts before macro dispatch

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #18047

🤖 Generated with Claude Code

@robobun

robobun commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator Author

Not adding Fixes #18047: that issue asks for tagged-template macros to work (the old Zig TODO panic path), which is what #30545 implements. This PR only fixes the scope-desync crash — after it, template macro invocations still intentionally fail with template literal macro invocations are not supported, so #18047 should stay open until the feature lands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any issues — the reorder is sound and mirrors e_call — but since this is in the core parser visit pass, a quick human glance would be good before merge.

Extended reasoning...

Overview

This PR reorders the e_template visit in src/js_parser/visit/visit_expr.rs so that template interpolation parts are visited immediately after the tag and before the macro-dispatch block. Previously, all four macro-dispatch outcomes (dead-flow, no_macros, node_modules, macro-call failure) returned early without visiting the parts, leaving their parse-pass scope entries unconsumed and tripping the Scope mismatch while visiting panic on the next scope push. The fix is a pure code-motion: the existing for part in e_.parts_mut() loop moves up ~90 lines, and the enclosing if let Some(tag) is split into two checks so the loop sits between them. Three regression tests are added (live tag, dead-flow, namespace-member tag), plus a one-line Cargo.lock sync for bun_bin's bstr dep.

I verified that the non-macro path (regular templates and non-macro tagged templates) has identical visit order before and after: tag → parts → fold. The only behavioral change is for macro-tagged templates, where parts are now visited before the early returns — which is exactly the invariant the scope-replay mechanism requires, and matches how e_call orders its args visit (lines ~2019-2021) before its macro dispatch (lines ~2174+). The tag binding moving from pre-visit to post-visit only affects tag.loc in one error message, which is benign.

Security risks

None. No auth, crypto, permissions, or untrusted-input handling is touched. The change is internal AST-visit ordering.

Level of scrutiny

Moderate-to-high. The diff itself is small, mechanical, and follows the same pattern as three prior merged fixes in this bug family (#31231 / #31340 / #31533). However, visit_expr.rs is on the hot path for every JS/TS file Bun transpiles, so a regression here has very wide blast radius. That's the only reason I'm deferring rather than approving outright.

Other factors

  • No bugs flagged by the bug-hunting system.
  • No CODEOWNERS cover these paths.
  • Regression tests cover all three reported variants and assert both the absence of the panic and the presence of the intended error/output.
  • The PR notes that the related macro/transpiler/template-literal/bundler_string suites pass locally.
  • The Cargo.lock change is a trivial lockfile regeneration from a prior commit's manifest edit.

@Jarred-Sumner Jarred-Sumner merged commit 64ae83c into main Jun 2, 2026
77 of 78 checks passed
@Jarred-Sumner Jarred-Sumner deleted the farm/9e30c2bd/fix-template-macro-scope-mismatch branch June 2, 2026 04:18
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.

2 participants