Skip to main content

mars_agents/resolve/
context.rs

1use std::collections::{BTreeMap, HashMap};
2
3use indexmap::IndexMap;
4
5use super::filter::push_filter_constraint;
6use super::{
7    PackageResolutionState, PackageVersions, PendingItem, RegisteredPackage, ResolvedGraph,
8    ResolvedNode, RootedSourceRef, VersionConstraint, VisitedSet,
9};
10use crate::config::FilterMode;
11use crate::source::ResolvedRef;
12use crate::types::{SourceId, SourceName};
13
14/// Mutable resolver state threaded through bottom-up resolution and DFS traversal.
15pub struct ResolverContext {
16    registry: IndexMap<SourceName, RegisteredPackage>,
17    package_states: HashMap<SourceName, PackageResolutionState>,
18    id_index: HashMap<SourceId, SourceName>,
19    version_constraints: HashMap<SourceName, Vec<(String, VersionConstraint)>>,
20    materialization_filters: HashMap<SourceName, Vec<FilterMode>>,
21    stack: Vec<PendingItem>,
22    visited: VisitedSet,
23    package_versions: PackageVersions,
24    /// Version overrides carried from a prior restart pass.
25    ///
26    /// When a restart is triggered because package X would resolve to a different
27    /// version under the full accumulated constraint set, the driver carries the
28    /// correct (new) ref into the fresh context via this map. The first-resolution
29    /// branch in `resolve_package_bottom_up` checks this map and uses the override
30    /// directly, so the same constraint-accumulation pattern does NOT re-trigger a
31    /// restart on the next pass.
32    version_overrides: HashMap<
33        SourceName,
34        (
35            ResolvedRef,
36            RootedSourceRef,
37            crate::staging::HookSurfaceState,
38        ),
39    >,
40    /// Pending restart info set by `resolve_package_bottom_up` just before it returns
41    /// `ResolutionRestartNeeded`. The driver reads this before discarding the context.
42    pending_restart: Option<(
43        SourceName,
44        ResolvedRef,
45        RootedSourceRef,
46        crate::staging::HookSurfaceState,
47    )>,
48    unreadable_hook_surfaces: BTreeMap<SourceName, std::collections::BTreeSet<String>>,
49}
50
51impl Default for ResolverContext {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57impl ResolverContext {
58    pub fn new() -> Self {
59        Self {
60            registry: IndexMap::new(),
61            package_states: HashMap::new(),
62            id_index: HashMap::new(),
63            version_constraints: HashMap::new(),
64            materialization_filters: HashMap::new(),
65            stack: Vec::new(),
66            visited: VisitedSet::new(),
67            package_versions: PackageVersions::new(),
68            version_overrides: HashMap::new(),
69            pending_restart: None,
70            unreadable_hook_surfaces: BTreeMap::new(),
71        }
72    }
73
74    /// Set version overrides from a prior restart pass.
75    /// These are used by `resolve_package_bottom_up` to skip re-resolution for
76    /// packages where the correct version was already computed.
77    pub(super) fn set_version_overrides(
78        &mut self,
79        overrides: HashMap<
80            SourceName,
81            (
82                ResolvedRef,
83                RootedSourceRef,
84                crate::staging::HookSurfaceState,
85            ),
86        >,
87    ) {
88        self.version_overrides = overrides;
89    }
90
91    /// Look up an override for the first resolution of `name`.
92    /// Returns the pre-computed ref and rooted package if present.
93    pub(super) fn version_override(
94        &self,
95        name: &SourceName,
96    ) -> Option<(
97        ResolvedRef,
98        RootedSourceRef,
99        crate::staging::HookSurfaceState,
100    )> {
101        self.version_overrides.get(name).cloned()
102    }
103
104    /// Record the restart info: the package that triggered a restart and the ref
105    /// it should be resolved to on the next pass. Called by `resolve_package_bottom_up`
106    /// just before returning `ResolutionRestartNeeded`.
107    pub(super) fn set_pending_restart(
108        &mut self,
109        package: SourceName,
110        new_ref: ResolvedRef,
111        new_rooted: RootedSourceRef,
112        hook_surface: crate::staging::HookSurfaceState,
113    ) {
114        self.pending_restart = Some((package, new_ref, new_rooted, hook_surface));
115    }
116
117    /// Drain the pending restart info. Called by the driver after catching the signal.
118    pub(super) fn take_pending_restart(
119        &mut self,
120    ) -> Option<(
121        SourceName,
122        ResolvedRef,
123        RootedSourceRef,
124        crate::staging::HookSurfaceState,
125    )> {
126        self.pending_restart.take()
127    }
128
129    pub(super) fn set_hook_surface(
130        &mut self,
131        source_name: &SourceName,
132        state: crate::staging::HookSurfaceState,
133    ) {
134        if let crate::staging::HookSurfaceState::Unreadable { hook_names } = state {
135            self.unreadable_hook_surfaces
136                .insert(source_name.clone(), hook_names);
137        }
138    }
139
140    pub(super) fn registry(&self) -> &IndexMap<SourceName, RegisteredPackage> {
141        &self.registry
142    }
143
144    pub(super) fn registry_mut(&mut self) -> &mut IndexMap<SourceName, RegisteredPackage> {
145        &mut self.registry
146    }
147
148    pub(super) fn package_states(&self) -> &HashMap<SourceName, PackageResolutionState> {
149        &self.package_states
150    }
151
152    pub(super) fn package_states_mut(
153        &mut self,
154    ) -> &mut HashMap<SourceName, PackageResolutionState> {
155        &mut self.package_states
156    }
157
158    pub(super) fn id_index(&self) -> &HashMap<SourceId, SourceName> {
159        &self.id_index
160    }
161
162    pub(super) fn id_index_mut(&mut self) -> &mut HashMap<SourceId, SourceName> {
163        &mut self.id_index
164    }
165
166    pub(super) fn version_constraints(
167        &self,
168    ) -> &HashMap<SourceName, Vec<(String, VersionConstraint)>> {
169        &self.version_constraints
170    }
171
172    pub(super) fn materialization_filters(&self) -> &HashMap<SourceName, Vec<FilterMode>> {
173        &self.materialization_filters
174    }
175
176    pub(super) fn visited(&self) -> &VisitedSet {
177        &self.visited
178    }
179
180    pub(super) fn visited_mut(&mut self) -> &mut VisitedSet {
181        &mut self.visited
182    }
183
184    pub(super) fn package_versions_mut(&mut self) -> &mut PackageVersions {
185        &mut self.package_versions
186    }
187
188    pub fn add_version_constraint(
189        &mut self,
190        package: &SourceName,
191        requester: &str,
192        constraint: VersionConstraint,
193    ) {
194        self.version_constraints
195            .entry(package.clone())
196            .or_default()
197            .push((requester.to_string(), constraint));
198    }
199
200    pub fn add_filter(&mut self, package: &SourceName, filter: FilterMode) {
201        push_filter_constraint(&mut self.materialization_filters, package, &filter);
202    }
203
204    pub fn push_pending(&mut self, item: PendingItem) {
205        self.stack.push(item);
206    }
207
208    pub fn pop_pending(&mut self) -> Option<PendingItem> {
209        self.stack.pop()
210    }
211
212    pub fn into_graph(self) -> ResolvedGraph {
213        let mut nodes: IndexMap<SourceName, ResolvedNode> = IndexMap::new();
214        for (name, package) in self.registry {
215            nodes.insert(name, package.node);
216        }
217
218        let mut order: Vec<SourceName> = nodes.keys().cloned().collect();
219        order.sort();
220
221        ResolvedGraph {
222            nodes,
223            order,
224            filters: self.materialization_filters,
225            version_constraints: self.version_constraints,
226            unreadable_hook_surfaces: self.unreadable_hook_surfaces,
227        }
228    }
229}