swc-project/swc · error
You should perform this operation in the closure passed to `
Error message
You should perform this operation in the closure passed to `set` of {}::{} What it means
Runtime panic from better_scoped_tls (used throughout swc for COMMENTS, hygiene/marks, etc.). `ScopedKey::with` requires the thread-local slot to be set, which only happens inside the closure passed to `KEY.set(&value, ...)`. In debug builds the crate overrides the panic message to name the module and key; in release the underlying scoped_tls still panics with its own message.
Source
Thrown at crates/better_scoped_tls/src/lib.rs:68
#[cfg_attr(not(debug_assertions), inline(always))]
pub fn set<F, R>(&'static self, t: &T, f: F) -> R
where
F: FnOnce() -> R,
{
self.inner.set(t, f)
}
/// See [scoped_tls::ScopedKey] for actual documentation.
#[track_caller]
#[cfg_attr(not(debug_assertions), inline(always))]
pub fn with<F, R>(&'static self, f: F) -> R
where
F: FnOnce(&T) -> R,
{
#[cfg(debug_assertions)]
if !self.inner.is_set() {
// Override panic message
panic!(
"You should perform this operation in the closure passed to `set` of {}::{}",
self.module_path, self.name
)
}
self.inner.with(f)
}
/// See [scoped_tls::ScopedKey] for actual documentation.
#[cfg_attr(not(debug_assertions), inline(always))]
pub fn is_set(&'static self) -> bool {
self.inner.is_set()
}
}
#[cfg(test)]
mod tests {
View on GitHub (pinned to 5176682b65)
Solutions
- Wrap the entry point in the key's set closure: `COMMENTS.set(&comments, || { ... })`
- If the code runs on another thread, call `.set(...)` again inside that thread before using `.with`
- Restructure to pass Comments/Handler explicitly as parameters instead of relying on TLS
Example fix
// before
fn my_pass() {
let c = COMMENTS.with(|c| c.clone()); // panics: not inside set
}
// after
fn my_pass(comments: &Rc<RefCell<Comments>>) {
COMMENTS.set(comments, || {
let c = COMMENTS.with(|c| c.clone());
// ...
});
} Defensive patterns
Strategy: validation
Validate before calling
if COMMENTS.is_set() {
COMMENTS.with(|c| {
// safe here
});
} else {
// not inside a `set` scope: initialize it or return an error
return Err(anyhow::anyhow!("COMMENTS scope not entered"));
} Try / catch
let r = std::panic::catch_unwind(|| {
COMMENTS.with(|c| c.clone())
});
let comments = match r {
Ok(c) => c,
Err(_) => return Err(anyhow::anyhow!("TLS key not set: called outside `set` closure")),
}; Prevention
- Enter swc APIs only inside the corresponding `KEY.set(&value, closure)` scope
- Remember `set` is per-thread: re-enter it inside any spawned thread
- Prefer passing Comments/Handler explicitly over TLS access in library code
- Test in debug builds — that is where the descriptive message fires
When it happens
Trigger: Calling `COMMENTS.with(|c| ...)` (or any swc TLS accessor) from code that runs outside the corresponding `.set(...)` closure — e.g. invoking a transform pass manually, calling from a freshly spawned thread, or from a drop handler after the set scope ended.
Common situations: Writing custom visitors/plugins that touch comments or hygiene data without entering through the swc compiler entry points; `set` is per-thread, so work moved to another thread loses the scope; debug builds surface it with this exact message.
Related errors
- An element descriptor's .kind property must be either "metho
- An element descriptor's .placement property must be one of "
- A class descriptor's .kind property must be "class", but a d
- ${objectType} can't have a .${name} property.
- Expected '${name}' to be a function
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/71b630590acc9378.
Report an issue: GitHub.