Skip to main content

solverforge_solver/builder/context/provider/
registry.rs

1use std::collections::HashSet;
2use std::fmt;
3use std::sync::Arc;
4
5use super::super::{
6    ConflictRepair, RuntimeScalarSlot, RuntimeScalarSlotId, ScalarGroupBinding,
7    ScalarGroupBindingKind,
8};
9use super::native::{pull_static_group, pull_static_repair};
10use super::types::PanicProviderErrorBoundary;
11use super::{
12    ProviderNormalizationState, ProviderReasonArena, RawProviderCandidate,
13    ResolvedProviderCandidate, RuntimeConflictRepairProviderBinding,
14    RuntimeHostProviderErrorBoundary, RuntimeProviderHandle, RuntimeProviderLimits,
15    RuntimeProviderSlotResolver, RuntimeScalarGroupProviderBinding,
16    StaticConflictRepairProviderBinding, StaticScalarGroupProviderBinding,
17};
18use crate::{RepairCandidate, RepairLimits, ScalarCandidate};
19
20/// Frozen schema-order registry. It does not contain a mutable host lookup;
21/// plans store immutable declaration indexes and cursor dispatch rebuilds no
22/// schema state at solve time.
23pub struct RuntimeProviderRegistry<S> {
24    groups: Vec<RuntimeScalarGroupProviderBinding<S>>,
25    repairs: Vec<RuntimeConflictRepairProviderBinding<S>>,
26    static_groups: Vec<StaticScalarGroupProviderBinding<S>>,
27    static_repairs: Vec<StaticConflictRepairProviderBinding<S>>,
28    error_boundary: Arc<dyn RuntimeHostProviderErrorBoundary>,
29    resolver: Option<RuntimeProviderSlotResolver<S>>,
30}
31
32impl<S> Default for RuntimeProviderRegistry<S> {
33    fn default() -> Self {
34        Self {
35            groups: Vec::new(),
36            repairs: Vec::new(),
37            static_groups: Vec::new(),
38            static_repairs: Vec::new(),
39            error_boundary: Arc::new(PanicProviderErrorBoundary),
40            resolver: None,
41        }
42    }
43}
44
45impl<S> Clone for RuntimeProviderRegistry<S> {
46    fn clone(&self) -> Self {
47        Self {
48            groups: self.groups.clone(),
49            repairs: self.repairs.clone(),
50            static_groups: self.static_groups.clone(),
51            static_repairs: self.static_repairs.clone(),
52            error_boundary: Arc::clone(&self.error_boundary),
53            resolver: self.resolver.clone(),
54        }
55    }
56}
57
58impl<S> fmt::Debug for RuntimeProviderRegistry<S> {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        f.debug_struct("RuntimeProviderRegistry")
61            .field("groups", &self.groups)
62            .field("repairs", &self.repairs)
63            .field("static_groups", &self.static_groups)
64            .field("static_repairs", &self.static_repairs)
65            .field("resolver_bound", &self.resolver.is_some())
66            .finish()
67    }
68}
69
70impl<S: 'static> RuntimeProviderRegistry<S> {
71    pub fn new(
72        groups: Vec<RuntimeScalarGroupProviderBinding<S>>,
73        repairs: Vec<RuntimeConflictRepairProviderBinding<S>>,
74        error_boundary: Arc<dyn RuntimeHostProviderErrorBoundary>,
75    ) -> Result<Self, String> {
76        let mut group_names = HashSet::new();
77        let mut previous_group_schema_index = None;
78        for group in &groups {
79            if group.group_name.is_empty() {
80                return Err("runtime provider registry has an empty group name".to_string());
81            }
82            if previous_group_schema_index.is_some_and(|previous| previous >= group.declared_index)
83            {
84                return Err(format!(
85                    "runtime provider group `{}` has non-monotonic declared schema index {}",
86                    group.group_name, group.declared_index
87                ));
88            }
89            previous_group_schema_index = Some(group.declared_index);
90            if !group_names.insert(Arc::clone(&group.group_name)) {
91                return Err(format!(
92                    "runtime provider registry declares callback group `{}` more than once",
93                    group.group_name
94                ));
95            }
96        }
97        let mut previous_repair_schema_index = None;
98        for repair in &repairs {
99            if previous_repair_schema_index
100                .is_some_and(|previous| previous >= repair.declared_index)
101            {
102                return Err(format!(
103                    "runtime repair provider has non-monotonic declared schema index {}",
104                    repair.declared_index
105                ));
106            }
107            previous_repair_schema_index = Some(repair.declared_index);
108        }
109        Ok(Self {
110            groups,
111            repairs,
112            static_groups: Vec::new(),
113            static_repairs: Vec::new(),
114            error_boundary,
115            resolver: None,
116        })
117    }
118
119    pub fn is_empty(&self) -> bool {
120        self.groups.is_empty()
121            && self.repairs.is_empty()
122            && self.static_groups.is_empty()
123            && self.static_repairs.is_empty()
124    }
125
126    pub fn groups(&self) -> &[RuntimeScalarGroupProviderBinding<S>] {
127        &self.groups
128    }
129
130    pub fn repairs(&self) -> &[RuntimeConflictRepairProviderBinding<S>] {
131        &self.repairs
132    }
133
134    pub fn static_groups(&self) -> &[StaticScalarGroupProviderBinding<S>] {
135        &self.static_groups
136    }
137
138    pub fn static_repairs(&self) -> &[StaticConflictRepairProviderBinding<S>] {
139        &self.static_repairs
140    }
141
142    pub fn group_indices(&self, group_name: &str) -> Vec<usize> {
143        self.groups
144            .iter()
145            .enumerate()
146            .filter_map(|(index, group)| (group.group_name.as_ref() == group_name).then_some(index))
147            .collect()
148    }
149
150    /// Whether one frozen repair source declares a configured constraint.
151    pub fn declares_constraint(&self, handle: RuntimeProviderHandle, constraint: &str) -> bool {
152        match handle {
153            RuntimeProviderHandle::CallbackRepair(index) => self
154                .repairs
155                .get(index)
156                .unwrap_or_else(|| {
157                    panic!("compiled callback repair provider index {index} no longer exists")
158                })
159                .declared_constraints
160                .iter()
161                .any(|name| name.as_ref() == constraint),
162            RuntimeProviderHandle::StaticRepair(index) => {
163                self.static_repairs
164                    .get(index)
165                    .unwrap_or_else(|| {
166                        panic!("compiled static repair provider index {index} no longer exists")
167                    })
168                    .repair
169                    .constraint_name()
170                    == constraint
171            }
172            RuntimeProviderHandle::CallbackGroup(_) | RuntimeProviderHandle::StaticGroup(_) => {
173                false
174            }
175        }
176    }
177
178    pub fn declares_any_constraint(
179        &self,
180        handle: RuntimeProviderHandle,
181        constraints: &[String],
182    ) -> bool {
183        constraints
184            .iter()
185            .any(|constraint| self.declares_constraint(handle, constraint))
186    }
187
188    pub(crate) fn freeze(
189        &mut self,
190        scalar_slots: &[RuntimeScalarSlot<S>],
191        scalar_groups: &[ScalarGroupBinding<S>],
192        conflict_repairs: &[ConflictRepair<S>],
193    ) -> Result<(), String> {
194        self.static_groups.clear();
195        self.static_repairs.clear();
196        for (declared_index, group) in scalar_groups.iter().enumerate() {
197            let ScalarGroupBindingKind::Candidates { candidate_provider } = &group.kind else {
198                continue;
199            };
200            self.static_groups.push(StaticScalarGroupProviderBinding {
201                declared_index,
202                group_name: group.group_name,
203                provider: *candidate_provider,
204                declared_limits: group.limits,
205            });
206        }
207        for (declared_index, repair) in conflict_repairs.iter().enumerate() {
208            self.static_repairs
209                .push(StaticConflictRepairProviderBinding {
210                    declared_index,
211                    repair: *repair,
212                });
213        }
214        if self.is_empty() {
215            return Ok(());
216        }
217        self.resolver = Some(RuntimeProviderSlotResolver::new(scalar_slots.to_vec())?);
218        Ok(())
219    }
220
221    fn resolver(&self) -> &RuntimeProviderSlotResolver<S> {
222        self.resolver.as_ref().unwrap_or_else(|| {
223            panic!("runtime provider registry was invoked before descriptor resolution")
224        })
225    }
226
227    /// Pulls exactly one source without normalizing or reordering its result.
228    pub fn pull_callback_raw(
229        &self,
230        handle: RuntimeProviderHandle,
231        solution: &S,
232        limits: RuntimeProviderLimits,
233    ) -> Vec<RawProviderCandidate> {
234        let _ = self.resolver();
235        let callback = match handle {
236            RuntimeProviderHandle::CallbackGroup(index) => self
237                .groups
238                .get(index)
239                .unwrap_or_else(|| {
240                    panic!("compiled callback group provider index {index} no longer exists")
241                })
242                .callback
243                .as_ref(),
244            RuntimeProviderHandle::CallbackRepair(index) => self
245                .repairs
246                .get(index)
247                .unwrap_or_else(|| {
248                    panic!("compiled callback repair provider index {index} no longer exists")
249                })
250                .callback
251                .as_ref(),
252            RuntimeProviderHandle::StaticGroup(_) | RuntimeProviderHandle::StaticRepair(_) => {
253                panic!("static providers must use their concrete pull path")
254            }
255        };
256        callback.pull(solution, limits)
257    }
258
259    pub(crate) fn pull_static_group(
260        &self,
261        index: usize,
262        solution: &S,
263        value_candidate_limit: Option<usize>,
264        max_moves_per_step: Option<usize>,
265    ) -> Vec<ScalarCandidate<S>> {
266        let _ = self.resolver();
267        let binding = self.static_groups.get(index).unwrap_or_else(|| {
268            panic!("compiled static group provider index {index} no longer exists")
269        });
270        pull_static_group(binding, solution, value_candidate_limit, max_moves_per_step)
271    }
272
273    pub(crate) fn pull_static_repair(
274        &self,
275        index: usize,
276        solution: &S,
277        limits: RepairLimits,
278    ) -> Vec<RepairCandidate<S>> {
279        let _ = self.resolver();
280        let binding = self.static_repairs.get(index).unwrap_or_else(|| {
281            panic!("compiled static repair provider index {index} no longer exists")
282        });
283        pull_static_repair(binding, solution, limits)
284    }
285
286    /// Resolves one raw result in the caller-owned deduplication scope.
287    /// Callback panics propagate from [`Self::pull_callback_raw`]; only structured core
288    /// normalization failures cross the host error boundary here.
289    pub fn normalize_or_raise(
290        &self,
291        solution: &S,
292        raw: Vec<RawProviderCandidate>,
293        allowed_slots: &[RuntimeScalarSlotId],
294        state: &mut ProviderNormalizationState,
295        reasons: &mut ProviderReasonArena,
296    ) -> Vec<ResolvedProviderCandidate<S>> {
297        match self.resolver().resolve_and_normalize_with_state(
298            solution,
299            raw,
300            allowed_slots,
301            state,
302            reasons,
303        ) {
304            Ok(candidates) => candidates,
305            Err(error) => self.error_boundary.raise(error),
306        }
307    }
308
309    pub(crate) fn normalize_static_group(
310        &self,
311        solution: &S,
312        candidates: Vec<ScalarCandidate<S>>,
313        allowed_slots: &[RuntimeScalarSlotId],
314        state: &mut ProviderNormalizationState,
315        reasons: &mut ProviderReasonArena,
316    ) -> Vec<ResolvedProviderCandidate<S>> {
317        self.resolver()
318            .resolve_static_group_and_normalize_with_state(
319                solution,
320                candidates,
321                allowed_slots,
322                state,
323                reasons,
324            )
325            .unwrap_or_else(|error| panic!("{error}"))
326    }
327
328    pub(crate) fn normalize_static_repair(
329        &self,
330        solution: &S,
331        candidates: Vec<RepairCandidate<S>>,
332        allowed_slots: &[RuntimeScalarSlotId],
333        state: &mut ProviderNormalizationState,
334        reasons: &mut ProviderReasonArena,
335    ) -> Vec<ResolvedProviderCandidate<S>> {
336        self.resolver()
337            .resolve_static_repair_and_normalize_with_state(
338                solution,
339                candidates,
340                allowed_slots,
341                state,
342                reasons,
343            )
344            .unwrap_or_else(|error| panic!("{error}"))
345    }
346}