Repository: nushell/nushell
File: crates/nu-protocol/src/engine/engine_state.rs (Rust)

Code before:
            // If config was updated in the stack, replace it.
            self.config = config;

            // Make plugin GC config changes take effect immediately.
            #[cfg(feature = "plugin")]
            self.update_plugin_gc_configs(&self.config.plugin_gc);
        }

        Ok(())
    }

    /// Clean up unused variables from a Stack to prevent memory leaks.
    /// This removes variables that are no longer referenced by any overlay.
    pub fn cleanup_stack_variables(&self, stack: &mut Stack) {
        use std::collections::HashSet;

        // Collect all VarIds that are still referenced by overlays
        let referenced_vars: HashSet<_> = self
            .scope
            .overlays
            .iter()
            .flat_map(|(_, overlay)| overlay.vars.values())
            .copied()
            // Always preserve critical system variables
            .chain([NU_VARIABLE_ID, IN_VARIABLE_ID, ENV_VARIABLE_ID])
            .collect();

        // Remove variables from stack that are no longer referenced
        stack
            .vars
            .retain(|(var_id, _)| referenced_vars.contains(var_id));
    }

    pub fn active_overlay_ids<'a, 'b>(
        &'b self,
        removed_overlays: &'a [Vec<u8>],
    ) -> impl DoubleEndedIterator<Item = &'b OverlayId> + 'a
    where
        'b: 'a,
    {
        self.scope.active_overlays.iter().filter(|id| {
            !removed_overlays
                .iter()
                .any(|name| name == self.get_overlay_name(**id))
        })
    }

    pub fn active_overlays<'a, 'b>(
        &'b self,
        removed_overlays: &'a [Vec<u8>],
    ) -> impl DoubleEndedIterator<Item = &'b OverlayFrame> + 'a

Review comment (suggestion):
Maybe we can do better with a whitelist of shadowed variables, which could be marked from:

https://github.com/nushell/nushell/blob/f015409253bf56ad6854cdaa7b69b6fa6a2b98f6/crates/nu-protocol/src/engine/overlay.rs#L211-L213

Just an unverified thought, will test it later

Code after:
        std::env::set_current_dir(cwd).map_err(|err| {
            IoError::new_internal(err, "Could not set current dir", crate::location!())
        })?;

        if let Some(config) = stack.config.take() {
            // If config was updated in the stack, replace it.
            self.config = config;

            // Make plugin GC config changes take effect immediately.
            #[cfg(feature = "plugin")]
            self.update_plugin_gc_configs(&self.config.plugin_gc);
        }

        Ok(())
    }

    /// Clean up unused variables from a Stack to prevent memory leaks.
    /// This removes variables that are no longer referenced by any overlay.
    pub fn cleanup_stack_variables(&mut self, stack: &mut Stack) {
        use std::collections::HashSet;

        let mut shadowed_vars = HashSet::new();
        for (_, frame) in self.scope.overlays.iter_mut() {
            shadowed_vars.extend(frame.shadowed_vars.to_owned());
            frame.shadowed_vars.clear();
        }

        // Remove variables from stack that are no longer referenced
        stack
            .vars
            .retain(|(var_id, _)| !shadowed_vars.contains(var_id));
    }

    pub fn active_overlay_ids<'a, 'b>(
        &'b self,
        removed_overlays: &'a [Vec<u8>],
    ) -> impl DoubleEndedIterator<Item = &'b OverlayId> + 'a
    where
        'b: 'a,
    {
        self.scope.active_overlays.iter().filter(|id| {
            !removed_overlays
                .iter()
                .any(|name| name == self.get_overlay_name(**id))
        })
    }

    pub fn active_overlays<'a, 'b>(
        &'b self,
        removed_overlays: &'a [Vec<u8>],
    ) -> impl DoubleEndedIterator<Item = &'b OverlayFrame> + 'a