Skip to main content

sema_core/runtime/
eval_task_context.rs

1use std::any::Any;
2use std::cell::RefCell;
3use std::collections::BTreeMap;
4use std::path::PathBuf;
5use std::rc::Rc;
6
7use crate::cycle::GcEdge;
8use crate::runtime::{IdCounter, IdExhausted, ScopeId, TaskLocalValue, Trace};
9use crate::Value;
10
11#[derive(Clone, Debug)]
12struct Scoped<T> {
13    id: Option<ScopeId>,
14    value: T,
15}
16
17impl<T> Scoped<T> {
18    fn inherited(value: T) -> Self {
19        Self { id: None, value }
20    }
21
22    fn owned(id: ScopeId, value: T) -> Self {
23        Self {
24            id: Some(id),
25            value,
26        }
27    }
28}
29
30/// Stable identity for one published dynamic-stack entry.
31///
32/// Pointer identity is deliberate: a mutation journal keeps the old token
33/// alive, so an allocator cannot recycle its address while a stale pop can
34/// still compare against it. Equal replacement values therefore remain
35/// distinguishable without a wrapping numeric generation.
36#[derive(Clone)]
37pub(crate) struct DynamicStackEntryId(Rc<()>);
38
39impl DynamicStackEntryId {
40    fn fresh() -> Self {
41        Self(Rc::new(()))
42    }
43}
44
45impl std::fmt::Debug for DynamicStackEntryId {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        f.write_str("DynamicStackEntryId(..)")
48    }
49}
50
51impl PartialEq for DynamicStackEntryId {
52    fn eq(&self, other: &Self) -> bool {
53        Rc::ptr_eq(&self.0, &other.0)
54    }
55}
56
57impl Eq for DynamicStackEntryId {}
58
59/// Interpreter-wide identity sidecar for dynamic context stacks.
60///
61/// The public stack values retain their legacy shape. This sidecar gives each
62/// entry an opaque identity that root snapshots clone and publication checks.
63#[derive(Clone, Debug, Default)]
64pub(crate) struct DynamicStackIdentities {
65    entries: BTreeMap<Value, Vec<DynamicStackEntryId>>,
66}
67
68impl DynamicStackIdentities {
69    pub(crate) fn from_stacks(stacks: &BTreeMap<Value, Vec<Value>>) -> Self {
70        Self {
71            entries: stacks
72                .iter()
73                .map(|(key, values)| {
74                    (
75                        key.clone(),
76                        values
77                            .iter()
78                            .map(|_| DynamicStackEntryId::fresh())
79                            .collect(),
80                    )
81                })
82                .collect(),
83        }
84    }
85
86    pub(crate) fn matches_stacks(&self, stacks: &BTreeMap<Value, Vec<Value>>) -> bool {
87        self.entries.len() == stacks.len()
88            && stacks.iter().all(|(key, values)| {
89                self.entries
90                    .get(key)
91                    .is_some_and(|identities| identities.len() == values.len())
92            })
93    }
94
95    pub(crate) fn push(&mut self, key: Value) {
96        self.entries
97            .entry(key)
98            .or_default()
99            .push(DynamicStackEntryId::fresh());
100    }
101
102    pub(crate) fn pop(&mut self, key: &Value) -> bool {
103        let Some(entries) = self.entries.get_mut(key) else {
104            return false;
105        };
106        if entries.pop().is_none() {
107            return false;
108        }
109        if entries.is_empty() {
110            self.entries.remove(key);
111        }
112        true
113    }
114
115    pub(crate) fn remove(&mut self, key: &Value) {
116        self.entries.remove(key);
117    }
118
119    fn entries_for(&self, key: &Value, len: usize) -> Vec<DynamicStackEntryId> {
120        let identities = self.entries.get(key).map(Vec::as_slice).unwrap_or(&[]);
121        assert_eq!(
122            identities.len(),
123            len,
124            "dynamic stack identities must match their value entries"
125        );
126        identities.to_vec()
127    }
128
129    fn push_exact(&mut self, key: Value, identity: DynamicStackEntryId) {
130        self.entries.entry(key).or_default().push(identity);
131    }
132
133    fn pop_exact(&mut self, key: &Value, expected: &DynamicStackEntryId) -> bool {
134        let Some(entries) = self.entries.get_mut(key) else {
135            return false;
136        };
137        if entries.last() != Some(expected) {
138            return false;
139        }
140        entries.pop();
141        if entries.is_empty() {
142            self.entries.remove(key);
143        }
144        true
145    }
146}
147
148#[derive(Clone, Debug)]
149struct DynamicStackEntry {
150    scope: Option<ScopeId>,
151    identity: DynamicStackEntryId,
152    value: Value,
153}
154
155impl DynamicStackEntry {
156    fn inherited(identity: DynamicStackEntryId, value: Value) -> Self {
157        Self {
158            scope: None,
159            identity,
160            value,
161        }
162    }
163
164    fn owned(scope: ScopeId, value: Value) -> Self {
165        Self {
166            scope: Some(scope),
167            identity: DynamicStackEntryId::fresh(),
168            value,
169        }
170    }
171}
172
173#[derive(Clone, Debug, Default)]
174struct ModuleState {
175    scope_ids: IdCounter<ScopeId>,
176    current_files: Vec<Scoped<PathBuf>>,
177    loading: Vec<Scoped<PathBuf>>,
178    exports: Vec<Scoped<Option<Vec<String>>>>,
179}
180
181#[derive(Debug)]
182pub struct ModuleTaskState {
183    inner: RefCell<ModuleState>,
184}
185
186impl Default for ModuleTaskState {
187    fn default() -> Self {
188        Self {
189            inner: RefCell::new(ModuleState::default()),
190        }
191    }
192}
193
194impl ModuleTaskState {
195    pub fn from_snapshot(
196        current_files: Vec<PathBuf>,
197        loading: Vec<PathBuf>,
198        exports: Vec<Option<Vec<String>>>,
199    ) -> Self {
200        Self {
201            inner: RefCell::new(ModuleState {
202                scope_ids: IdCounter::new(),
203                current_files: current_files.into_iter().map(Scoped::inherited).collect(),
204                loading: loading.into_iter().map(Scoped::inherited).collect(),
205                exports: exports.into_iter().map(Scoped::inherited).collect(),
206            }),
207        }
208    }
209
210    pub fn current_file(&self) -> Option<PathBuf> {
211        self.inner
212            .borrow()
213            .current_files
214            .last()
215            .map(|entry| entry.value.clone())
216    }
217
218    pub fn push_current_file(&self, path: PathBuf) -> Result<ScopeId, IdExhausted> {
219        let mut inner = self.inner.borrow_mut();
220        let id = inner.scope_ids.allocate()?;
221        inner.current_files.push(Scoped::owned(id, path));
222        Ok(id)
223    }
224
225    pub fn remove_current_file(&self, id: ScopeId) -> bool {
226        remove_scope(&mut self.inner.borrow_mut().current_files, id).is_some()
227    }
228
229    pub fn pop_current_file(&self) -> Option<PathBuf> {
230        self.inner
231            .borrow_mut()
232            .current_files
233            .pop()
234            .map(|entry| entry.value)
235    }
236
237    pub fn loading(&self) -> Vec<PathBuf> {
238        self.inner
239            .borrow()
240            .loading
241            .iter()
242            .map(|entry| entry.value.clone())
243            .collect()
244    }
245
246    pub fn push_loading(&self, path: PathBuf) -> Result<ScopeId, IdExhausted> {
247        let mut inner = self.inner.borrow_mut();
248        let id = inner.scope_ids.allocate()?;
249        inner.loading.push(Scoped::owned(id, path));
250        Ok(id)
251    }
252
253    pub fn remove_loading(&self, id: ScopeId) -> bool {
254        remove_scope(&mut self.inner.borrow_mut().loading, id).is_some()
255    }
256
257    pub fn exports(&self) -> Vec<Option<Vec<String>>> {
258        self.inner
259            .borrow()
260            .exports
261            .iter()
262            .map(|entry| entry.value.clone())
263            .collect()
264    }
265
266    pub fn push_exports(&self, exports: Option<Vec<String>>) -> Result<ScopeId, IdExhausted> {
267        let mut inner = self.inner.borrow_mut();
268        let id = inner.scope_ids.allocate()?;
269        inner.exports.push(Scoped::owned(id, exports));
270        Ok(id)
271    }
272
273    pub fn set_current_exports(&self, exports: Vec<String>) -> bool {
274        let mut inner = self.inner.borrow_mut();
275        let Some(current) = inner.exports.last_mut() else {
276            return false;
277        };
278        current.value = Some(exports);
279        true
280    }
281
282    pub fn take_exports(&self, id: ScopeId) -> Option<Option<Vec<String>>> {
283        remove_scope(&mut self.inner.borrow_mut().exports, id)
284    }
285
286    pub fn remove_exports(&self, id: ScopeId) -> bool {
287        self.take_exports(id).is_some()
288    }
289
290    pub fn pop_exports(&self) -> Option<Option<Vec<String>>> {
291        self.inner
292            .borrow_mut()
293            .exports
294            .pop()
295            .map(|entry| entry.value)
296    }
297}
298
299impl Trace for ModuleTaskState {
300    fn trace(&self, _sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
301        self.inner.try_borrow().is_ok()
302    }
303}
304
305impl TaskLocalValue for ModuleTaskState {
306    fn inherit(&self) -> Rc<dyn TaskLocalValue> {
307        let mut inner = self.inner.borrow().clone();
308        strip_scope_ids(&mut inner.current_files);
309        strip_scope_ids(&mut inner.loading);
310        strip_scope_ids(&mut inner.exports);
311        Rc::new(Self {
312            inner: RefCell::new(inner),
313        })
314    }
315
316    fn as_any(&self) -> &dyn Any {
317        self
318    }
319}
320
321#[derive(Clone, Debug, Eq, PartialEq)]
322pub(crate) enum DynamicMutation {
323    UserSet(Value, Value),
324    UserRemove(Value),
325    UserClear,
326    HiddenSet(Value, Value),
327    StackPush {
328        key: Value,
329        value: Value,
330        scope: ScopeId,
331        identity: DynamicStackEntryId,
332    },
333    /// Removes an inherited top only if publication still sees that exact
334    /// entry. An equal value recreated later carries a different identity.
335    StackPop {
336        key: Value,
337        expected: DynamicStackEntryId,
338    },
339}
340
341#[derive(Clone, Debug)]
342struct DynamicState {
343    scope_ids: IdCounter<ScopeId>,
344    user_frames: Vec<Scoped<BTreeMap<Value, Value>>>,
345    hidden_frames: Vec<Scoped<BTreeMap<Value, Value>>>,
346    stacks: BTreeMap<Value, Vec<DynamicStackEntry>>,
347    mutations: Option<Vec<DynamicMutation>>,
348}
349
350#[derive(Debug)]
351pub struct DynamicTaskState {
352    inner: RefCell<DynamicState>,
353}
354
355impl DynamicTaskState {
356    pub fn root(
357        user_frames: Vec<BTreeMap<Value, Value>>,
358        hidden_frames: Vec<BTreeMap<Value, Value>>,
359        stacks: BTreeMap<Value, Vec<Value>>,
360    ) -> Self {
361        let identities = DynamicStackIdentities::from_stacks(&stacks);
362        Self::root_with_stack_identities(user_frames, hidden_frames, stacks, &identities)
363    }
364
365    pub(crate) fn root_with_stack_identities(
366        mut user_frames: Vec<BTreeMap<Value, Value>>,
367        mut hidden_frames: Vec<BTreeMap<Value, Value>>,
368        stacks: BTreeMap<Value, Vec<Value>>,
369        identities: &DynamicStackIdentities,
370    ) -> Self {
371        if user_frames.is_empty() {
372            user_frames.push(BTreeMap::new());
373        }
374        if hidden_frames.is_empty() {
375            hidden_frames.push(BTreeMap::new());
376        }
377        Self {
378            inner: RefCell::new(DynamicState {
379                scope_ids: IdCounter::new(),
380                user_frames: user_frames.into_iter().map(Scoped::inherited).collect(),
381                hidden_frames: hidden_frames.into_iter().map(Scoped::inherited).collect(),
382                stacks: stacks
383                    .into_iter()
384                    .map(|(key, values)| {
385                        let entries = identities
386                            .entries_for(&key, values.len())
387                            .into_iter()
388                            .zip(values)
389                            .map(|(identity, value)| DynamicStackEntry::inherited(identity, value))
390                            .collect();
391                        (key, entries)
392                    })
393                    .collect(),
394                mutations: Some(Vec::new()),
395            }),
396        }
397    }
398
399    pub fn user_get(&self, key: &Value) -> Option<Value> {
400        self.inner
401            .borrow()
402            .user_frames
403            .iter()
404            .rev()
405            .find_map(|frame| frame.value.get(key).cloned())
406    }
407
408    pub fn user_all(&self) -> BTreeMap<Value, Value> {
409        self.inner
410            .borrow()
411            .user_frames
412            .iter()
413            .flat_map(|frame| frame.value.iter())
414            .map(|(key, value)| (key.clone(), value.clone()))
415            .collect()
416    }
417
418    pub fn user_set(&self, key: Value, value: Value) {
419        let mut inner = self.inner.borrow_mut();
420        let publishes = inner
421            .user_frames
422            .last()
423            .is_some_and(|frame| frame.id.is_none());
424        if let Some(frame) = inner.user_frames.last_mut() {
425            frame.value.insert(key.clone(), value.clone());
426        }
427        if publishes {
428            record(&mut inner, DynamicMutation::UserSet(key, value));
429        }
430    }
431
432    pub fn user_remove(&self, key: &Value) -> Option<Value> {
433        let mut inner = self.inner.borrow_mut();
434        let publishes = inner
435            .user_frames
436            .iter()
437            .any(|frame| frame.id.is_none() && frame.value.contains_key(key));
438        let mut removed = None;
439        for frame in inner.user_frames.iter_mut().rev() {
440            if let Some(value) = frame.value.remove(key) {
441                if removed.is_none() {
442                    removed = Some(value);
443                }
444            }
445        }
446        if publishes {
447            record(&mut inner, DynamicMutation::UserRemove(key.clone()));
448        }
449        removed
450    }
451
452    pub fn user_clear(&self) {
453        let mut inner = self.inner.borrow_mut();
454        inner.user_frames.clear();
455        inner.user_frames.push(Scoped::inherited(BTreeMap::new()));
456        record(&mut inner, DynamicMutation::UserClear);
457    }
458
459    pub fn push_user_frame(
460        &self,
461        bindings: BTreeMap<Value, Value>,
462    ) -> Result<ScopeId, IdExhausted> {
463        let mut inner = self.inner.borrow_mut();
464        let id = inner.scope_ids.allocate()?;
465        inner.user_frames.push(Scoped::owned(id, bindings));
466        Ok(id)
467    }
468
469    pub fn remove_user_frame(&self, id: ScopeId) -> bool {
470        remove_scope(&mut self.inner.borrow_mut().user_frames, id).is_some()
471    }
472
473    pub fn pop_user_frame(&self) -> bool {
474        let mut inner = self.inner.borrow_mut();
475        if inner.user_frames.len() <= 1 {
476            return false;
477        }
478        inner.user_frames.pop();
479        true
480    }
481
482    pub fn hidden_get(&self, key: &Value) -> Option<Value> {
483        self.inner
484            .borrow()
485            .hidden_frames
486            .iter()
487            .rev()
488            .find_map(|frame| frame.value.get(key).cloned())
489    }
490
491    pub fn hidden_set(&self, key: Value, value: Value) {
492        let mut inner = self.inner.borrow_mut();
493        let publishes = inner
494            .hidden_frames
495            .last()
496            .is_some_and(|frame| frame.id.is_none());
497        if let Some(frame) = inner.hidden_frames.last_mut() {
498            frame.value.insert(key.clone(), value.clone());
499        }
500        if publishes {
501            record(&mut inner, DynamicMutation::HiddenSet(key, value));
502        }
503    }
504
505    pub fn push_hidden_frame(
506        &self,
507        bindings: BTreeMap<Value, Value>,
508    ) -> Result<ScopeId, IdExhausted> {
509        let mut inner = self.inner.borrow_mut();
510        let id = inner.scope_ids.allocate()?;
511        inner.hidden_frames.push(Scoped::owned(id, bindings));
512        Ok(id)
513    }
514
515    pub fn remove_hidden_frame(&self, id: ScopeId) -> bool {
516        remove_scope(&mut self.inner.borrow_mut().hidden_frames, id).is_some()
517    }
518
519    pub fn pop_hidden_frame(&self) -> bool {
520        let mut inner = self.inner.borrow_mut();
521        if inner.hidden_frames.len() <= 1 {
522            return false;
523        }
524        inner.hidden_frames.pop();
525        true
526    }
527
528    pub fn stack_get(&self, key: &Value) -> Vec<Value> {
529        self.inner
530            .borrow()
531            .stacks
532            .get(key)
533            .map(|values| values.iter().map(|entry| entry.value.clone()).collect())
534            .unwrap_or_default()
535    }
536
537    pub fn stack_push(&self, key: Value, value: Value) -> Result<ScopeId, IdExhausted> {
538        let mut inner = self.inner.borrow_mut();
539        let id = inner.scope_ids.allocate()?;
540        let entry = DynamicStackEntry::owned(id, value.clone());
541        let identity = entry.identity.clone();
542        inner.stacks.entry(key.clone()).or_default().push(entry);
543        record(
544            &mut inner,
545            DynamicMutation::StackPush {
546                key,
547                value,
548                scope: id,
549                identity,
550            },
551        );
552        Ok(id)
553    }
554
555    pub fn stack_pop(&self, key: &Value) -> Option<Value> {
556        let mut inner = self.inner.borrow_mut();
557        let entry = inner.stacks.get_mut(key)?.pop()?;
558        if inner.stacks.get(key).is_some_and(Vec::is_empty) {
559            inner.stacks.remove(key);
560        }
561        if entry
562            .scope
563            .is_some_and(|scope| cancel_pending_stack_push(&mut inner, scope))
564        {
565            return Some(entry.value);
566        }
567        record(
568            &mut inner,
569            DynamicMutation::StackPop {
570                key: key.clone(),
571                expected: entry.identity,
572            },
573        );
574        Some(entry.value)
575    }
576
577    pub fn remove_stack_value(&self, key: &Value, id: ScopeId) -> Option<Value> {
578        let mut inner = self.inner.borrow_mut();
579        let stack = inner.stacks.get_mut(key)?;
580        let index = stack.iter().position(|entry| entry.scope == Some(id))?;
581        let was_top = index + 1 == stack.len();
582        let removed = stack.remove(index);
583        if stack.is_empty() {
584            inner.stacks.remove(key);
585        }
586        if !cancel_pending_stack_push(&mut inner, id) && was_top {
587            record(
588                &mut inner,
589                DynamicMutation::StackPop {
590                    key: key.clone(),
591                    expected: removed.identity.clone(),
592                },
593            );
594        }
595        Some(removed.value)
596    }
597
598    pub(crate) fn drain_mutations(&self) -> Option<Vec<DynamicMutation>> {
599        self.inner
600            .borrow_mut()
601            .mutations
602            .as_mut()
603            .map(std::mem::take)
604    }
605}
606
607impl Trace for DynamicTaskState {
608    fn trace(&self, sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
609        let Ok(inner) = self.inner.try_borrow() else {
610            return false;
611        };
612        for frame in inner.user_frames.iter().chain(&inner.hidden_frames) {
613            for (key, value) in &frame.value {
614                sink(GcEdge::Value(key));
615                sink(GcEdge::Value(value));
616            }
617        }
618        for (key, values) in &inner.stacks {
619            sink(GcEdge::Value(key));
620            for value in values {
621                sink(GcEdge::Value(&value.value));
622            }
623        }
624        if let Some(mutations) = &inner.mutations {
625            for mutation in mutations {
626                trace_mutation(mutation, sink);
627            }
628        }
629        true
630    }
631}
632
633impl TaskLocalValue for DynamicTaskState {
634    fn inherit(&self) -> Rc<dyn TaskLocalValue> {
635        let mut inner = self.inner.borrow().clone();
636        strip_scope_ids(&mut inner.user_frames);
637        strip_scope_ids(&mut inner.hidden_frames);
638        for stack in inner.stacks.values_mut() {
639            for entry in stack {
640                entry.scope = None;
641            }
642        }
643        inner.mutations = None;
644        Rc::new(Self {
645            inner: RefCell::new(inner),
646        })
647    }
648
649    fn as_any(&self) -> &dyn Any {
650        self
651    }
652}
653
654fn remove_scope<T>(entries: &mut Vec<Scoped<T>>, id: ScopeId) -> Option<T> {
655    entries
656        .iter()
657        .position(|entry| entry.id == Some(id))
658        .map(|index| entries.remove(index).value)
659}
660
661fn strip_scope_ids<T>(entries: &mut [Scoped<T>]) {
662    for entry in entries {
663        entry.id = None;
664    }
665}
666
667fn record(inner: &mut DynamicState, mutation: DynamicMutation) {
668    if let Some(mutations) = &mut inner.mutations {
669        mutations.push(mutation);
670    }
671}
672
673fn cancel_pending_stack_push(inner: &mut DynamicState, scope: ScopeId) -> bool {
674    let Some(mutations) = inner.mutations.as_mut() else {
675        return false;
676    };
677    let Some(index) = mutations.iter().rposition(
678        |mutation| matches!(mutation, DynamicMutation::StackPush { scope: candidate, .. } if *candidate == scope),
679    ) else {
680        return false;
681    };
682    mutations.remove(index);
683    true
684}
685
686/// Applies a root's settlement journal to the interpreter-wide dynamic state.
687pub(crate) fn apply_dynamic_mutations(
688    user_frames: &mut Vec<BTreeMap<Value, Value>>,
689    hidden_frames: &mut Vec<BTreeMap<Value, Value>>,
690    stacks: &mut BTreeMap<Value, Vec<Value>>,
691    stack_identities: &mut DynamicStackIdentities,
692    mutations: &[DynamicMutation],
693) {
694    assert!(
695        stack_identities.matches_stacks(stacks),
696        "dynamic stack identities must match their value entries"
697    );
698    if user_frames.is_empty() {
699        user_frames.push(BTreeMap::new());
700    }
701    if hidden_frames.is_empty() {
702        hidden_frames.push(BTreeMap::new());
703    }
704    for mutation in mutations {
705        match mutation {
706            DynamicMutation::UserSet(key, value) => {
707                user_frames
708                    .last_mut()
709                    .expect("user context is normalized above")
710                    .insert(key.clone(), value.clone());
711            }
712            DynamicMutation::UserRemove(key) => {
713                for frame in user_frames.iter_mut().rev() {
714                    frame.remove(key);
715                }
716            }
717            DynamicMutation::UserClear => {
718                user_frames.clear();
719                user_frames.push(BTreeMap::new());
720            }
721            DynamicMutation::HiddenSet(key, value) => {
722                hidden_frames
723                    .last_mut()
724                    .expect("hidden context is normalized above")
725                    .insert(key.clone(), value.clone());
726            }
727            DynamicMutation::StackPush {
728                key,
729                value,
730                identity,
731                ..
732            } => {
733                stacks.entry(key.clone()).or_default().push(value.clone());
734                stack_identities.push_exact(key.clone(), identity.clone());
735            }
736            DynamicMutation::StackPop { key, expected } => {
737                if stack_identities.pop_exact(key, expected) {
738                    let stack = stacks
739                        .get_mut(key)
740                        .expect("stack identity must have a matching value stack");
741                    stack
742                        .pop()
743                        .expect("stack identity must have a matching value entry");
744                    if stack.is_empty() {
745                        stacks.remove(key);
746                    }
747                }
748            }
749        }
750    }
751}
752
753fn trace_mutation(mutation: &DynamicMutation, sink: &mut dyn FnMut(GcEdge<'_>)) {
754    match mutation {
755        DynamicMutation::UserSet(key, value)
756        | DynamicMutation::HiddenSet(key, value)
757        | DynamicMutation::StackPush { key, value, .. } => {
758            sink(GcEdge::Value(key));
759            sink(GcEdge::Value(value));
760        }
761        DynamicMutation::UserRemove(key) => {
762            sink(GcEdge::Value(key));
763        }
764        DynamicMutation::StackPop { key, .. } => {
765            sink(GcEdge::Value(key));
766        }
767        DynamicMutation::UserClear => {}
768    }
769}
770
771#[cfg(test)]
772mod tests {
773    use std::collections::BTreeMap;
774    use std::path::PathBuf;
775    use std::rc::Rc;
776
777    use crate::cycle::GcEdge;
778    use crate::runtime::{TaskContext, Trace};
779    use crate::Value;
780
781    use super::{
782        apply_dynamic_mutations, DynamicMutation, DynamicStackIdentities, DynamicTaskState,
783        ModuleTaskState,
784    };
785
786    #[test]
787    fn module_scopes_remove_the_exact_current_file_token_once() {
788        let state = ModuleTaskState::default();
789        let first = state
790            .push_current_file(PathBuf::from("first.sema"))
791            .expect("scope ID available");
792        let second = state
793            .push_current_file(PathBuf::from("second.sema"))
794            .expect("scope ID available");
795
796        assert!(state.remove_current_file(first));
797        assert!(!state.remove_current_file(first));
798        assert_eq!(state.current_file(), Some(PathBuf::from("second.sema")));
799        assert!(state.remove_current_file(second));
800        assert_eq!(state.current_file(), None);
801    }
802
803    #[test]
804    fn module_scopes_remove_the_exact_loading_token_once() {
805        let state = ModuleTaskState::default();
806        let first = state
807            .push_loading(PathBuf::from("first.sema"))
808            .expect("scope ID available");
809        let second = state
810            .push_loading(PathBuf::from("second.sema"))
811            .expect("scope ID available");
812
813        assert!(state.remove_loading(first));
814        assert!(!state.remove_loading(first));
815        assert_eq!(state.loading(), vec![PathBuf::from("second.sema")]);
816        assert!(state.remove_loading(second));
817        assert!(state.loading().is_empty());
818    }
819
820    #[test]
821    fn module_scopes_remove_the_exact_export_token_once() {
822        let state = ModuleTaskState::default();
823        let first = state
824            .push_exports(Some(vec!["first".to_owned()]))
825            .expect("scope ID available");
826        let second = state
827            .push_exports(Some(vec!["second".to_owned()]))
828            .expect("scope ID available");
829
830        assert!(state.remove_exports(first));
831        assert!(!state.remove_exports(first));
832        assert_eq!(state.exports(), vec![Some(vec!["second".to_owned()])]);
833        assert!(state.remove_exports(second));
834        assert!(state.exports().is_empty());
835    }
836
837    #[test]
838    fn module_exports_update_and_take_the_exact_scope() {
839        let state = ModuleTaskState::default();
840        let outer = state.push_exports(None).expect("scope ID available");
841        let inner = state.push_exports(None).expect("scope ID available");
842
843        assert!(state.set_current_exports(vec!["inner".to_owned()]));
844        assert_eq!(
845            state.take_exports(outer),
846            Some(None),
847            "taking an outer scope must not consume the current scope"
848        );
849        assert_eq!(
850            state.take_exports(inner),
851            Some(Some(vec!["inner".to_owned()]))
852        );
853        assert_eq!(state.take_exports(inner), None);
854    }
855
856    #[test]
857    fn module_child_snapshot_is_independent() {
858        let parent = Rc::new(ModuleTaskState::from_snapshot(
859            vec![PathBuf::from("parent.sema")],
860            vec![PathBuf::from("loading.sema")],
861            vec![Some(vec!["parent-export".to_owned()])],
862        ));
863        let mut context = TaskContext::default();
864        context.insert(Rc::clone(&parent));
865
866        let child = context.inherit_for_child();
867        let child = child
868            .get_rc::<ModuleTaskState>()
869            .expect("module state inherited");
870        let child_file = child
871            .push_current_file(PathBuf::from("child.sema"))
872            .expect("scope ID available");
873        let child_loading = child
874            .push_loading(PathBuf::from("child-loading.sema"))
875            .expect("scope ID available");
876        let child_exports = child.push_exports(None).expect("scope ID available");
877
878        assert_eq!(parent.current_file(), Some(PathBuf::from("parent.sema")));
879        assert_eq!(parent.loading(), vec![PathBuf::from("loading.sema")]);
880        assert_eq!(
881            parent.exports(),
882            vec![Some(vec!["parent-export".to_owned()])]
883        );
884        assert!(child.remove_current_file(child_file));
885        assert!(child.remove_loading(child_loading));
886        assert!(child.remove_exports(child_exports));
887    }
888
889    #[test]
890    fn module_child_strips_parent_scope_tokens() {
891        let parent = Rc::new(ModuleTaskState::default());
892        let file = parent
893            .push_current_file(PathBuf::from("parent.sema"))
894            .expect("scope ID available");
895        let loading = parent
896            .push_loading(PathBuf::from("loading.sema"))
897            .expect("scope ID available");
898        let exports = parent
899            .push_exports(Some(vec!["parent-export".to_owned()]))
900            .expect("scope ID available");
901        let mut context = TaskContext::default();
902        context.insert(Rc::clone(&parent));
903
904        let child = context
905            .inherit_for_child()
906            .get_rc::<ModuleTaskState>()
907            .expect("module state inherited");
908
909        assert!(!child.remove_current_file(file));
910        assert!(!child.remove_loading(loading));
911        assert!(!child.remove_exports(exports));
912        assert_eq!(child.current_file(), Some(PathBuf::from("parent.sema")));
913        assert_eq!(child.loading(), vec![PathBuf::from("loading.sema")]);
914        assert_eq!(
915            child.exports(),
916            vec![Some(vec!["parent-export".to_owned()])]
917        );
918        let child_scope = child
919            .push_current_file(PathBuf::from("child.sema"))
920            .expect("scope ID available");
921        assert!(child_scope > exports);
922    }
923
924    #[test]
925    fn dynamic_scope_cleanup_is_exact_and_idempotent() {
926        let state = DynamicTaskState::root(
927            vec![BTreeMap::new()],
928            vec![BTreeMap::new()],
929            BTreeMap::new(),
930        );
931        let outer_user = state
932            .push_user_frame(BTreeMap::from([(
933                Value::keyword("frame"),
934                Value::string("outer"),
935            )]))
936            .expect("scope ID available");
937        let inner_user = state
938            .push_user_frame(BTreeMap::from([(
939                Value::keyword("frame"),
940                Value::string("inner"),
941            )]))
942            .expect("scope ID available");
943        let outer_hidden = state
944            .push_hidden_frame(BTreeMap::new())
945            .expect("scope ID available");
946        let inner_hidden = state
947            .push_hidden_frame(BTreeMap::new())
948            .expect("scope ID available");
949        let stack_key = Value::keyword("stack");
950        let outer_stack = state
951            .stack_push(stack_key.clone(), Value::string("outer"))
952            .expect("scope ID available");
953        let inner_stack = state
954            .stack_push(stack_key.clone(), Value::string("inner"))
955            .expect("scope ID available");
956
957        assert!(state.remove_user_frame(outer_user));
958        assert!(!state.remove_user_frame(outer_user));
959        assert_eq!(
960            state.user_get(&Value::keyword("frame")),
961            Some(Value::string("inner"))
962        );
963        assert!(state.remove_hidden_frame(outer_hidden));
964        assert!(!state.remove_hidden_frame(outer_hidden));
965        assert_eq!(
966            state.remove_stack_value(&stack_key, outer_stack),
967            Some(Value::string("outer"))
968        );
969        assert_eq!(state.remove_stack_value(&stack_key, outer_stack), None);
970        assert_eq!(state.stack_get(&stack_key), vec![Value::string("inner")]);
971
972        assert!(state.remove_user_frame(inner_user));
973        assert!(state.remove_hidden_frame(inner_hidden));
974        assert_eq!(
975            state.remove_stack_value(&stack_key, inner_stack),
976            Some(Value::string("inner"))
977        );
978
979        state
980            .push_user_frame(BTreeMap::new())
981            .expect("scope ID available");
982        state
983            .push_hidden_frame(BTreeMap::new())
984            .expect("scope ID available");
985        assert!(state.pop_user_frame());
986        assert!(state.pop_hidden_frame());
987        assert!(!state.pop_user_frame());
988        assert!(!state.pop_hidden_frame());
989    }
990
991    #[test]
992    fn root_dynamic_state_records_publishable_mutations_in_order() {
993        let state = DynamicTaskState::root(
994            vec![BTreeMap::new()],
995            vec![BTreeMap::new()],
996            BTreeMap::new(),
997        );
998        let user_key = Value::keyword("user");
999        let hidden_key = Value::keyword("hidden");
1000        let stack_key = Value::keyword("stack");
1001
1002        state.user_set(user_key.clone(), Value::int(1));
1003        assert_eq!(state.user_remove(&user_key), Some(Value::int(1)));
1004        state.user_clear();
1005        state.hidden_set(hidden_key.clone(), Value::int(2));
1006        state
1007            .stack_push(stack_key.clone(), Value::int(3))
1008            .expect("scope ID available");
1009        assert_eq!(state.stack_pop(&stack_key), Some(Value::int(3)));
1010
1011        assert_eq!(
1012            state.drain_mutations(),
1013            Some(vec![
1014                DynamicMutation::UserSet(user_key.clone(), Value::int(1)),
1015                DynamicMutation::UserRemove(user_key),
1016                DynamicMutation::UserClear,
1017                DynamicMutation::HiddenSet(hidden_key, Value::int(2)),
1018            ])
1019        );
1020        assert_eq!(state.drain_mutations(), Some(Vec::new()));
1021    }
1022
1023    #[test]
1024    fn inherited_stack_pop_cannot_remove_another_roots_equal_later_push() {
1025        let key = Value::keyword("stack");
1026        let initial = BTreeMap::from([(key.clone(), vec![Value::keyword("a")])]);
1027        let mut identities = DynamicStackIdentities::from_stacks(&initial);
1028        let first = DynamicTaskState::root_with_stack_identities(
1029            vec![BTreeMap::new()],
1030            vec![BTreeMap::new()],
1031            initial.clone(),
1032            &identities,
1033        );
1034        let second = DynamicTaskState::root_with_stack_identities(
1035            vec![BTreeMap::new()],
1036            vec![BTreeMap::new()],
1037            initial.clone(),
1038            &identities,
1039        );
1040
1041        first
1042            .stack_push(key.clone(), Value::keyword("a"))
1043            .expect("scope ID available");
1044        assert_eq!(second.stack_pop(&key), Some(Value::keyword("a")));
1045
1046        let mut user = vec![BTreeMap::new()];
1047        let mut hidden = vec![BTreeMap::new()];
1048        let mut published = initial;
1049        apply_dynamic_mutations(
1050            &mut user,
1051            &mut hidden,
1052            &mut published,
1053            &mut identities,
1054            &first.drain_mutations().expect("root journal"),
1055        );
1056        apply_dynamic_mutations(
1057            &mut user,
1058            &mut hidden,
1059            &mut published,
1060            &mut identities,
1061            &second.drain_mutations().expect("root journal"),
1062        );
1063
1064        assert_eq!(
1065            published.get(&key),
1066            Some(&vec![Value::keyword("a"), Value::keyword("a")])
1067        );
1068    }
1069
1070    #[test]
1071    fn stale_pop_cannot_remove_an_equal_entry_recreated_by_a_later_root() {
1072        let key = Value::keyword("stack");
1073        let value = Value::keyword("same");
1074        let initial = BTreeMap::from([(key.clone(), vec![value.clone()])]);
1075        let mut identities = DynamicStackIdentities::from_stacks(&initial);
1076        let stale = DynamicTaskState::root_with_stack_identities(
1077            vec![BTreeMap::new()],
1078            vec![BTreeMap::new()],
1079            initial.clone(),
1080            &identities,
1081        );
1082        let recreating = DynamicTaskState::root_with_stack_identities(
1083            vec![BTreeMap::new()],
1084            vec![BTreeMap::new()],
1085            initial.clone(),
1086            &identities,
1087        );
1088
1089        assert_eq!(stale.stack_pop(&key), Some(value.clone()));
1090        assert_eq!(recreating.stack_pop(&key), Some(value.clone()));
1091        recreating
1092            .stack_push(key.clone(), value.clone())
1093            .expect("scope ID available");
1094
1095        let mut user = vec![BTreeMap::new()];
1096        let mut hidden = vec![BTreeMap::new()];
1097        let mut published = initial;
1098        apply_dynamic_mutations(
1099            &mut user,
1100            &mut hidden,
1101            &mut published,
1102            &mut identities,
1103            &recreating.drain_mutations().expect("root journal"),
1104        );
1105        apply_dynamic_mutations(
1106            &mut user,
1107            &mut hidden,
1108            &mut published,
1109            &mut identities,
1110            &stale.drain_mutations().expect("root journal"),
1111        );
1112
1113        assert_eq!(published.get(&key), Some(&vec![value]));
1114    }
1115
1116    #[test]
1117    fn one_root_can_replay_multiple_inherited_pops_in_order() {
1118        let key = Value::keyword("stack");
1119        let initial = BTreeMap::from([(
1120            key.clone(),
1121            vec![Value::keyword("bottom"), Value::keyword("top")],
1122        )]);
1123        let mut identities = DynamicStackIdentities::from_stacks(&initial);
1124        let root = DynamicTaskState::root_with_stack_identities(
1125            vec![BTreeMap::new()],
1126            vec![BTreeMap::new()],
1127            initial.clone(),
1128            &identities,
1129        );
1130
1131        assert_eq!(root.stack_pop(&key), Some(Value::keyword("top")));
1132        assert_eq!(root.stack_pop(&key), Some(Value::keyword("bottom")));
1133
1134        let mut user = vec![BTreeMap::new()];
1135        let mut hidden = vec![BTreeMap::new()];
1136        let mut published = initial;
1137        apply_dynamic_mutations(
1138            &mut user,
1139            &mut hidden,
1140            &mut published,
1141            &mut identities,
1142            &root.drain_mutations().expect("root journal"),
1143        );
1144
1145        assert!(!published.contains_key(&key));
1146    }
1147
1148    #[test]
1149    fn owned_stack_cleanup_cancels_only_its_exact_push_with_duplicate_values() {
1150        let key = Value::keyword("stack");
1151        let state = DynamicTaskState::root(
1152            vec![BTreeMap::new()],
1153            vec![BTreeMap::new()],
1154            BTreeMap::new(),
1155        );
1156        let first = state
1157            .stack_push(key.clone(), Value::keyword("same"))
1158            .expect("scope ID available");
1159        let second = state
1160            .stack_push(key.clone(), Value::keyword("same"))
1161            .expect("scope ID available");
1162
1163        assert_eq!(
1164            state.remove_stack_value(&key, first),
1165            Some(Value::keyword("same"))
1166        );
1167        let mutations = state.drain_mutations().expect("root journal");
1168        assert!(matches!(
1169            mutations.as_slice(),
1170            [DynamicMutation::StackPush {
1171                key: mutation_key,
1172                value,
1173                scope,
1174                ..
1175            }] if mutation_key == &key && value == &Value::keyword("same") && *scope == second
1176        ));
1177
1178        let mut user = vec![BTreeMap::new()];
1179        let mut hidden = vec![BTreeMap::new()];
1180        let mut published = BTreeMap::new();
1181        let mut identities = DynamicStackIdentities::default();
1182        apply_dynamic_mutations(
1183            &mut user,
1184            &mut hidden,
1185            &mut published,
1186            &mut identities,
1187            &mutations,
1188        );
1189        assert_eq!(published.get(&key), Some(&vec![Value::keyword("same")]));
1190    }
1191
1192    #[test]
1193    fn dynamic_user_all_merges_frames_from_outer_to_inner() {
1194        let outer_only = Value::keyword("outer-only");
1195        let shadowed = Value::keyword("shadowed");
1196        let inner_only = Value::keyword("inner-only");
1197        let state = DynamicTaskState::root(
1198            vec![
1199                BTreeMap::from([
1200                    (outer_only.clone(), Value::int(1)),
1201                    (shadowed.clone(), Value::int(2)),
1202                ]),
1203                BTreeMap::from([
1204                    (shadowed.clone(), Value::int(3)),
1205                    (inner_only.clone(), Value::int(4)),
1206                ]),
1207            ],
1208            vec![BTreeMap::new()],
1209            BTreeMap::new(),
1210        );
1211
1212        assert_eq!(
1213            state.user_all(),
1214            BTreeMap::from([
1215                (outer_only, Value::int(1)),
1216                (shadowed, Value::int(3)),
1217                (inner_only, Value::int(4)),
1218            ])
1219        );
1220    }
1221
1222    #[test]
1223    fn inherited_top_frames_record_user_and_hidden_sets_for_publication() {
1224        let user_key = Value::keyword("user");
1225        let hidden_key = Value::keyword("hidden");
1226        let state = DynamicTaskState::root(
1227            vec![
1228                BTreeMap::from([(user_key.clone(), Value::int(1))]),
1229                BTreeMap::new(),
1230            ],
1231            vec![
1232                BTreeMap::from([(hidden_key.clone(), Value::int(2))]),
1233                BTreeMap::new(),
1234            ],
1235            BTreeMap::new(),
1236        );
1237
1238        state.user_set(user_key.clone(), Value::int(10));
1239        state.hidden_set(hidden_key.clone(), Value::int(20));
1240
1241        assert_eq!(
1242            state.drain_mutations(),
1243            Some(vec![
1244                DynamicMutation::UserSet(user_key, Value::int(10)),
1245                DynamicMutation::HiddenSet(hidden_key, Value::int(20)),
1246            ])
1247        );
1248    }
1249
1250    #[test]
1251    fn removing_a_key_from_any_inherited_frame_records_publication() {
1252        let key = Value::keyword("removed");
1253        let state = DynamicTaskState::root(
1254            vec![
1255                BTreeMap::new(),
1256                BTreeMap::from([(key.clone(), Value::int(1))]),
1257            ],
1258            vec![BTreeMap::new()],
1259            BTreeMap::new(),
1260        );
1261
1262        assert_eq!(state.user_remove(&key), Some(Value::int(1)));
1263        assert_eq!(
1264            state.drain_mutations(),
1265            Some(vec![DynamicMutation::UserRemove(key)])
1266        );
1267    }
1268
1269    #[test]
1270    fn dynamic_child_deep_clones_state_without_publication_authority() {
1271        let user_key = Value::keyword("user");
1272        let hidden_key = Value::keyword("hidden");
1273        let stack_key = Value::keyword("stack");
1274        let parent = Rc::new(DynamicTaskState::root(
1275            vec![BTreeMap::from([(user_key.clone(), Value::int(1))])],
1276            vec![BTreeMap::from([(hidden_key.clone(), Value::int(2))])],
1277            BTreeMap::from([(stack_key.clone(), vec![Value::int(3)])]),
1278        ));
1279        let mut context = TaskContext::default();
1280        context.insert(Rc::clone(&parent));
1281
1282        let child = context.inherit_for_child();
1283        let child = child
1284            .get_rc::<DynamicTaskState>()
1285            .expect("dynamic state inherited");
1286        child.user_set(user_key.clone(), Value::int(10));
1287        child.hidden_set(hidden_key.clone(), Value::int(20));
1288        child
1289            .stack_push(stack_key.clone(), Value::int(30))
1290            .expect("scope ID available");
1291
1292        assert_eq!(parent.user_get(&user_key), Some(Value::int(1)));
1293        assert_eq!(parent.hidden_get(&hidden_key), Some(Value::int(2)));
1294        assert_eq!(parent.stack_get(&stack_key), vec![Value::int(3)]);
1295        assert_eq!(child.user_get(&user_key), Some(Value::int(10)));
1296        assert_eq!(child.hidden_get(&hidden_key), Some(Value::int(20)));
1297        assert_eq!(
1298            child.stack_get(&stack_key),
1299            vec![Value::int(3), Value::int(30)]
1300        );
1301        assert_eq!(child.drain_mutations(), None);
1302        assert_eq!(parent.drain_mutations(), Some(Vec::new()));
1303    }
1304
1305    #[test]
1306    fn dynamic_child_strips_parent_scope_tokens() {
1307        let parent = Rc::new(DynamicTaskState::root(
1308            vec![BTreeMap::new()],
1309            vec![BTreeMap::new()],
1310            BTreeMap::new(),
1311        ));
1312        let user = parent
1313            .push_user_frame(BTreeMap::from([(Value::keyword("user"), Value::int(1))]))
1314            .expect("scope ID available");
1315        let hidden = parent
1316            .push_hidden_frame(BTreeMap::from([(Value::keyword("hidden"), Value::int(2))]))
1317            .expect("scope ID available");
1318        let stack_key = Value::keyword("stack");
1319        let stack = parent
1320            .stack_push(stack_key.clone(), Value::int(3))
1321            .expect("scope ID available");
1322        let mut context = TaskContext::default();
1323        context.insert(Rc::clone(&parent));
1324
1325        let child = context
1326            .inherit_for_child()
1327            .get_rc::<DynamicTaskState>()
1328            .expect("dynamic state inherited");
1329
1330        assert!(!child.remove_user_frame(user));
1331        assert!(!child.remove_hidden_frame(hidden));
1332        assert_eq!(child.remove_stack_value(&stack_key, stack), None);
1333        assert_eq!(child.user_get(&Value::keyword("user")), Some(Value::int(1)));
1334        assert_eq!(
1335            child.hidden_get(&Value::keyword("hidden")),
1336            Some(Value::int(2))
1337        );
1338        assert_eq!(child.stack_get(&stack_key), vec![Value::int(3)]);
1339        let child_scope = child
1340            .push_user_frame(BTreeMap::new())
1341            .expect("scope ID available");
1342        assert!(child_scope > stack);
1343    }
1344
1345    #[test]
1346    fn dynamic_child_preserves_inherited_stack_entry_identity() {
1347        let key = Value::keyword("stack");
1348        let parent = Rc::new(DynamicTaskState::root(
1349            vec![BTreeMap::new()],
1350            vec![BTreeMap::new()],
1351            BTreeMap::from([(key.clone(), vec![Value::keyword("value")])]),
1352        ));
1353        let mut context = TaskContext::default();
1354        context.insert(Rc::clone(&parent));
1355
1356        let child = context
1357            .inherit_for_child()
1358            .get_rc::<DynamicTaskState>()
1359            .expect("dynamic state inherited");
1360        let parent_identity = parent.inner.borrow().stacks[&key][0].identity.clone();
1361        let child_entry = child.inner.borrow().stacks[&key][0].clone();
1362
1363        assert_eq!(child_entry.identity, parent_identity);
1364        assert_eq!(child_entry.scope, None);
1365    }
1366
1367    #[test]
1368    fn conditional_stack_pop_traces_its_key() {
1369        let key = Value::keyword("stack");
1370        let state = DynamicTaskState::root(
1371            vec![BTreeMap::new()],
1372            vec![BTreeMap::new()],
1373            BTreeMap::from([(key.clone(), vec![Value::int(1)])]),
1374        );
1375        assert_eq!(state.stack_pop(&key), Some(Value::int(1)));
1376
1377        let mut edges = 0;
1378        assert!(state.trace(&mut |edge| {
1379            assert!(matches!(edge, GcEdge::Value(_)));
1380            edges += 1;
1381        }));
1382        assert_eq!(edges, 1);
1383    }
1384
1385    #[test]
1386    fn dynamic_trace_reports_every_stored_value_edge_exactly_once() {
1387        let state = DynamicTaskState::root(
1388            vec![BTreeMap::from([(Value::keyword("user"), Value::int(1))])],
1389            vec![BTreeMap::from([(Value::keyword("hidden"), Value::int(2))])],
1390            BTreeMap::from([(Value::keyword("stack"), vec![Value::int(3), Value::int(4)])]),
1391        );
1392        state.user_set(Value::keyword("published"), Value::int(5));
1393        state.hidden_set(Value::keyword("private"), Value::int(6));
1394        state
1395            .stack_push(Value::keyword("other-stack"), Value::int(7))
1396            .expect("scope ID available");
1397        state.user_remove(&Value::keyword("published"));
1398
1399        let mut edges = 0;
1400        assert!(state.trace(&mut |edge| {
1401            assert!(matches!(edge, GcEdge::Value(_)));
1402            edges += 1;
1403        }));
1404
1405        // Live state: user 2, hidden 4, stacks 5. Journal: 2 + 2 + 2 + 1.
1406        assert_eq!(edges, 18);
1407    }
1408}