solverforge_solver/builder/context/provider/
resolver.rs1use std::collections::HashSet;
2use std::sync::Arc;
3
4use super::super::{RuntimeScalarSlot, RuntimeScalarSlotId};
5use super::types::ProviderCandidateReason;
6use super::{
7 ProviderNormalizationState, ProviderReasonArena, ProviderResolutionError, RawProviderCandidate,
8 RawProviderEdit, ResolvedProviderCandidate, ResolvedProviderEdit,
9};
10use crate::{RepairCandidate, ScalarCandidate, ScalarEdit};
11
12#[derive(Debug)]
13struct RuntimeProviderSlot<S> {
14 id: RuntimeScalarSlotId,
15 slot: RuntimeScalarSlot<S>,
16}
17
18trait ProviderEditAccess {
19 fn entity_index(&self) -> usize;
20 fn to_value(&self) -> Option<usize>;
21}
22
23impl ProviderEditAccess for RawProviderEdit {
24 fn entity_index(&self) -> usize {
25 self.entity_index
26 }
27
28 fn to_value(&self) -> Option<usize> {
29 self.to_value
30 }
31}
32
33impl<S> ProviderEditAccess for ScalarEdit<S> {
34 fn entity_index(&self) -> usize {
35 self.entity_index()
36 }
37
38 fn to_value(&self) -> Option<usize> {
39 self.to_value()
40 }
41}
42
43impl<S> Clone for RuntimeProviderSlot<S> {
44 fn clone(&self) -> Self {
45 Self {
46 id: self.id.clone(),
47 slot: self.slot.clone(),
48 }
49 }
50}
51
52#[derive(Debug)]
58pub struct RuntimeProviderSlotResolver<S> {
59 slots: Vec<RuntimeProviderSlot<S>>,
60}
61
62impl<S> Clone for RuntimeProviderSlotResolver<S> {
63 fn clone(&self) -> Self {
64 Self {
65 slots: self.slots.clone(),
66 }
67 }
68}
69
70impl<S> RuntimeProviderSlotResolver<S> {
71 pub fn new(slots: Vec<RuntimeScalarSlot<S>>) -> Result<Self, String> {
72 let mut seen = HashSet::new();
73 let mut resolved = Vec::with_capacity(slots.len());
74 for slot in slots {
75 let id = slot.id();
76 if !seen.insert((id.descriptor_index, id.variable_index)) {
77 return Err(format!(
78 "runtime provider registry has duplicate scalar slot {}.{}",
79 id.entity_class, id.variable_name
80 ));
81 }
82 resolved.push(RuntimeProviderSlot { id, slot });
83 }
84 Ok(Self { slots: resolved })
85 }
86
87 pub fn slot_ids(&self) -> impl Iterator<Item = &RuntimeScalarSlotId> {
88 self.slots.iter().map(|slot| &slot.id)
89 }
90
91 fn resolve_raw_index(
92 &self,
93 edit: &RawProviderEdit,
94 allowed_slots: &[RuntimeScalarSlotId],
95 ) -> Result<usize, ProviderResolutionError> {
96 let matches = |slot: &RuntimeProviderSlot<S>| {
97 edit.entity_class
98 .as_deref()
99 .is_none_or(|entity| entity == slot.id.entity_class.as_ref())
100 && edit.variable_name.as_ref() == slot.id.variable_name.as_ref()
101 };
102 let Some(first_matching_index) = self.slots.iter().position(matches) else {
103 return Err(ProviderResolutionError::UnknownSlot {
104 entity_class: edit.entity_class.clone(),
105 variable_name: Arc::clone(&edit.variable_name),
106 });
107 };
108 let is_allowed = |slot: &RuntimeProviderSlot<S>| {
109 allowed_slots.iter().any(|candidate| {
110 candidate.descriptor_index == slot.id.descriptor_index
111 && candidate.variable_index == slot.id.variable_index
112 })
113 };
114 let Some(index) = self
115 .slots
116 .iter()
117 .position(|slot| matches(slot) && is_allowed(slot))
118 else {
119 let first_matching_slot = &self.slots[first_matching_index];
120 return Err(ProviderResolutionError::SlotOutsideSelector {
121 entity_class: Arc::clone(&first_matching_slot.id.entity_class),
122 variable_name: Arc::clone(&first_matching_slot.id.variable_name),
123 });
124 };
125 Ok(index)
126 }
127
128 fn resolve_static_index(
129 &self,
130 edit: &ScalarEdit<S>,
131 allowed_slots: &[RuntimeScalarSlotId],
132 ) -> Result<usize, ProviderResolutionError> {
133 let matches = |slot: &RuntimeProviderSlot<S>| {
134 slot.id.descriptor_index == edit.descriptor_index()
135 && slot.id.variable_name.as_ref() == edit.variable_name()
136 };
137 let Some(first_matching_index) = self.slots.iter().position(matches) else {
138 return Err(ProviderResolutionError::UnknownSlot {
139 entity_class: None,
140 variable_name: Arc::from(edit.variable_name()),
141 });
142 };
143 let is_allowed = |slot: &RuntimeProviderSlot<S>| {
144 allowed_slots.iter().any(|candidate| {
145 candidate.descriptor_index == slot.id.descriptor_index
146 && candidate.variable_index == slot.id.variable_index
147 })
148 };
149 let Some(index) = self
150 .slots
151 .iter()
152 .position(|slot| matches(slot) && is_allowed(slot))
153 else {
154 let first_matching_slot = &self.slots[first_matching_index];
155 return Err(ProviderResolutionError::SlotOutsideSelector {
156 entity_class: Arc::clone(&first_matching_slot.id.entity_class),
157 variable_name: Arc::clone(&first_matching_slot.id.variable_name),
158 });
159 };
160 Ok(index)
161 }
162
163 pub fn resolve_and_normalize(
164 &self,
165 solution: &S,
166 candidates: Vec<RawProviderCandidate>,
167 allowed_slots: &[RuntimeScalarSlotId],
168 reasons: &mut ProviderReasonArena,
169 ) -> Result<Vec<ResolvedProviderCandidate<S>>, ProviderResolutionError> {
170 self.resolve_and_normalize_with_state(
171 solution,
172 candidates,
173 allowed_slots,
174 &mut ProviderNormalizationState::default(),
175 reasons,
176 )
177 }
178
179 pub fn resolve_and_normalize_with_state(
182 &self,
183 solution: &S,
184 candidates: Vec<RawProviderCandidate>,
185 allowed_slots: &[RuntimeScalarSlotId],
186 state: &mut ProviderNormalizationState,
187 reasons: &mut ProviderReasonArena,
188 ) -> Result<Vec<ResolvedProviderCandidate<S>>, ProviderResolutionError> {
189 self.normalize_candidates(
190 solution,
191 candidates,
192 allowed_slots,
193 state,
194 reasons,
195 |candidate| {
196 (
197 ProviderCandidateReason::Host(candidate.reason),
198 candidate.edits,
199 )
200 },
201 |resolver, edit, allowed| resolver.resolve_raw_index(edit, allowed),
202 )
203 }
204
205 pub(super) fn resolve_static_group_and_normalize_with_state(
206 &self,
207 solution: &S,
208 candidates: Vec<ScalarCandidate<S>>,
209 allowed_slots: &[RuntimeScalarSlotId],
210 state: &mut ProviderNormalizationState,
211 reasons: &mut ProviderReasonArena,
212 ) -> Result<Vec<ResolvedProviderCandidate<S>>, ProviderResolutionError> {
213 self.normalize_candidates(
214 solution,
215 candidates,
216 allowed_slots,
217 state,
218 reasons,
219 |candidate| {
220 (
221 ProviderCandidateReason::Static(candidate.reason()),
222 candidate.into_edits(),
223 )
224 },
225 |resolver, edit, allowed| resolver.resolve_static_index(edit, allowed),
226 )
227 }
228
229 pub(super) fn resolve_static_repair_and_normalize_with_state(
230 &self,
231 solution: &S,
232 candidates: Vec<RepairCandidate<S>>,
233 allowed_slots: &[RuntimeScalarSlotId],
234 state: &mut ProviderNormalizationState,
235 reasons: &mut ProviderReasonArena,
236 ) -> Result<Vec<ResolvedProviderCandidate<S>>, ProviderResolutionError> {
237 self.normalize_candidates(
238 solution,
239 candidates,
240 allowed_slots,
241 state,
242 reasons,
243 |candidate| {
244 (
245 ProviderCandidateReason::Static(candidate.reason()),
246 candidate.into_edits(),
247 )
248 },
249 |resolver, edit, allowed| resolver.resolve_static_index(edit, allowed),
250 )
251 }
252
253 #[allow(clippy::too_many_arguments)]
254 fn normalize_candidates<C, E, Split, Resolve>(
255 &self,
256 solution: &S,
257 candidates: Vec<C>,
258 allowed_slots: &[RuntimeScalarSlotId],
259 state: &mut ProviderNormalizationState,
260 reasons: &mut ProviderReasonArena,
261 mut split: Split,
262 mut resolve: Resolve,
263 ) -> Result<Vec<ResolvedProviderCandidate<S>>, ProviderResolutionError>
264 where
265 E: ProviderEditAccess,
266 Split: FnMut(C) -> (ProviderCandidateReason, Vec<E>),
267 Resolve: FnMut(
268 &RuntimeProviderSlotResolver<S>,
269 &E,
270 &[RuntimeScalarSlotId],
271 ) -> Result<usize, ProviderResolutionError>,
272 {
273 let mut normalized = Vec::new();
274 for candidate in candidates {
275 let (reason, candidate_edits) = split(candidate);
276 if candidate_edits.is_empty() {
277 continue;
278 }
279 let mut edits = Vec::with_capacity(candidate_edits.len());
280 let mut seen_targets = HashSet::new();
281 let mut duplicate_target = false;
282 for edit in candidate_edits {
283 let slot_index = resolve(self, &edit, allowed_slots)?;
284 let slot = &self.slots[slot_index];
285 if !seen_targets.insert((
289 slot.id.descriptor_index,
290 slot.id.variable_index,
291 edit.entity_index(),
292 )) {
293 duplicate_target = true;
294 break;
295 }
296 let entity_index = edit.entity_index();
297 let to_value = edit.to_value();
298 if entity_index >= slot.slot.entity_count(solution) {
299 return Err(ProviderResolutionError::EntityIndexOutOfBounds {
300 entity_class: Arc::clone(&slot.id.entity_class),
301 variable_name: Arc::clone(&slot.id.variable_name),
302 entity_index,
303 });
304 }
305 if !slot.slot.value_is_legal(solution, entity_index, to_value) {
306 return Err(ProviderResolutionError::IllegalValue {
307 entity_class: Arc::clone(&slot.id.entity_class),
308 variable_name: Arc::clone(&slot.id.variable_name),
309 entity_index,
310 to_value,
311 });
312 }
313 edits.push(ResolvedProviderEdit {
314 descriptor_index: slot.id.descriptor_index,
315 variable_index: slot.id.variable_index,
316 slot: slot.slot.clone(),
317 entity_index,
318 to_value,
319 });
320 }
321 if duplicate_target {
322 continue;
323 }
324 let dedup_edits = edits
325 .iter()
326 .map(|edit| {
327 (
328 edit.descriptor_index,
329 edit.variable_index,
330 edit.entity_index,
331 edit.to_value,
332 )
333 })
334 .collect::<Vec<_>>();
335 let reason = reasons.intern_candidate(reason);
336 if !state.seen_candidates.insert((reason, dedup_edits)) {
337 continue;
338 }
339 normalized.push(ResolvedProviderCandidate { reason, edits });
340 }
341 Ok(normalized)
342 }
343}