Skip to main content

zsh/extensions/
subexp_cleanup.rs

1//! Rust-only cleanup guard for the transient `__subexp_arr_*` array
2//! temporaries that `paramsubst` (src/ported/subst.rs) stashes in the
3//! global paramtab while expanding an array sub-expression
4//! (`${(s: :)$(cmd)}`, `${${(s: :)v}[N]}`, …).
5//!
6//! In zsh C the sub-expression array lives in the per-paramsubst
7//! `Value` struct (`v.isarr = 1; aval = …`) and never touches the
8//! parameter hash, so it is invisible to `typeset -p`. zshrs reuses
9//! the canonical paramtab as scratch so the existing splat / subscript
10//! / filter code can resolve the temp through the normal var-lookup
11//! paths — but without cleanup that leaks `__subexp_arr_N` into
12//! user-visible state (it shows up in `typeset -p`, in `${(k)parameters}`,
13//! etc.). This guard evicts every temp its owning `paramsubst` call
14//! created when that call returns, on ALL paths — including the many
15//! early error returns and the recursive nested-`${…}` returns —
16//! restoring the C-visible behaviour that nothing leaks.
17//!
18//! This is deliberately NOT a `src/ported` helper: there is no zsh C
19//! function it corresponds to. It is pure zshrs bookkeeping that exists
20//! only because the port reuses the global paramtab as scratch, so it
21//! lives under `src/extensions` per the project's Rust-only-code rule.
22
23/// Tracks the `__subexp_arr_*` temp array names created during a single
24/// `paramsubst` invocation and evicts them from the global paramtab on
25/// `Drop` (i.e. when the invocation returns, on every code path).
26pub struct SubexpTempGuard {
27    names: Vec<String>,
28}
29
30impl SubexpTempGuard {
31    #[inline]
32    pub fn new() -> Self {
33        Self { names: Vec::new() }
34    }
35
36    /// Register a temp array name for eviction when this guard drops.
37    #[inline]
38    pub fn track(&mut self, name: String) {
39        self.names.push(name);
40    }
41}
42
43impl Default for SubexpTempGuard {
44    #[inline]
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl Drop for SubexpTempGuard {
51    fn drop(&mut self) {
52        if self.names.is_empty() {
53            return;
54        }
55        // Direct paramtab eviction (not `unsetparam`): these temps are
56        // plain `PM_ARRAY` nodes with no gsu / special-unset hooks, so a
57        // straight `remove` is both correct and free of side effects.
58        if let Ok(mut tab) = crate::ported::params::paramtab().write() {
59            for n in &self.names {
60                tab.remove(n);
61            }
62        }
63    }
64}