Skip to content

Commit f8d3ecc

Browse files
committed
Remove legacy standard stock config surface
1 parent 334f71d commit f8d3ecc

File tree

13 files changed

+129
-857
lines changed

13 files changed

+129
-857
lines changed

crates/solverforge-cvrp/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub trait VrpSolution {
4949
}
5050

5151
/* ============================================================================
52-
Free functions (callable as fn-pointer fields in ListSpec)
52+
Free functions (callable as fn-pointer fields in list stock hooks)
5353
============================================================================
5454
*/
5555

crates/solverforge-macros/WIREFRAME.md

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Applies to structs. Adds derives: `Clone, Debug, PartialEq, Eq, ProblemFactImpl`
5555
- `impl PlanningEntity for T``is_pinned()`, `as_any()`, `as_any_mut()`
5656
- `impl PlanningId for T` (if `#[planning_id]` present) — `type Id` set to field type, `planning_id()` returns field value
5757
- `impl T { pub fn entity_descriptor(solution_field: &'static str) -> EntityDescriptor }` — builds descriptor with all variable descriptors (genuine, list, shadow) and preserves `#[planning_id]` / `#[planning_pin]` metadata
58+
- `pub trait {Entity}UnassignedFilter<...>` (when the entity has exactly one `Option<_>` planning variable) — `.unassigned()` on `UniConstraintStream<_, Entity, ...>`
5859

5960
### `PlanningSolutionImpl`
6061

@@ -96,14 +97,12 @@ Applies to structs. Adds derives: `Clone, Debug, PartialEq, Eq, ProblemFactImpl`
9697
- `impl T { pub fn descriptor() -> SolutionDescriptor }` — builds full descriptor with entity extractors and fact extractors, reusing entity-generated descriptors so field-level variable metadata is preserved
9798
- `impl T { pub fn entity_count(&Self, descriptor_index: usize) -> usize }` — entity count by descriptor index
9899
- List operations (when list shadow support is configured): `list_len()`, `list_len_static()`, `list_remove()`, `list_insert()`, `list_get()`, `list_set()`, `list_reverse()`, `sublist_remove()`, `sublist_insert()`, `ruin_remove()`, `ruin_insert()`, `list_remove_for_construction()`, `index_to_element_static()`, `list_variable_descriptor_index()`, `element_count()`, `assigned_elements()`, `n_entities()`, `assign_element()`
99-
- Standard variable operations (legacy stock path only): `standard_get_variable()`, `standard_set_variable()`, `standard_value_count()`, `standard_entity_count()`, `standard_variable_descriptor_index()`, `standard_variable_field_name()`
100100
- `impl ShadowVariableSupport for T``update_entity_shadows()` (no-op if no shadow config; generates inverse/previous/next/cascading/aggregate/compute updates otherwise)
101-
- `impl SolvableSolution for T` (when any variable config present) — delegates to `descriptor()` and `entity_count()`
101+
- `impl SolvableSolution for T` — delegates to `descriptor()` and `entity_count()`
102102
- `impl Solvable for T` (when constraints path specified) — `solve()` calls `solve_internal()`
103103
- `impl Analyzable for T` (when constraints path specified) — `analyze()` creates `ScoreDirector` and returns `ScoreAnalysis`
104104
- `fn solve_internal()` (when constraints path specified) — calls `run_stock_solver()` for macro-generated stock solving (standard-only and list/mixed stock paths); explicit low-level `ProblemSpec` use remains on `run_solver()`
105105
- `pub trait {Name}ConstraintStreams<Sc>` — accessor methods for all `#[planning_entity_collection]` and `#[problem_fact_collection]` fields; implemented on `ConstraintFactory<{Name}, Sc>`
106-
- `pub trait {Entity}UnassignedFilter<Sc, E, F>` (when `standard_variable_config` present) — `.unassigned()` method on `UniConstraintStream` filtering entities where the planning variable is `None`
107106

108107
### `ProblemFactImpl`
109108

@@ -133,12 +132,10 @@ Applies to structs. Adds derives: `Clone, Debug, PartialEq, Eq, ProblemFactImpl`
133132
|----------|-----------|------|
134133
| `parse_constraints_path` | `fn(&[Attribute]) -> Option<String>` | Extracts `#[solverforge_constraints_path = "..."]` |
135134
| `parse_shadow_config` | `fn(&[Attribute]) -> ShadowConfig` | Parses `#[shadow_variable_updates(...)]` |
136-
| `parse_standard_variable_config` | `fn(&[Attribute]) -> StandardVariableConfig` | Parses `#[standard_variable_config(...)]` |
137135
| `generate_list_operations` | `fn(&ShadowConfig, &Fields, &Option<String>, &Ident) -> TokenStream` | Generates list variable methods + solve_internal |
138-
| `generate_standard_variable_operations` | `fn(&StandardVariableConfig, &Fields, &Option<String>, &Ident) -> TokenStream` | Generates standard variable methods |
139-
| `generate_solvable_solution` | `fn(&ShadowConfig, &StandardVariableConfig, &Ident, &Option<String>) -> TokenStream` | Generates SolvableSolution/Solvable/Analyzable impls |
136+
| `generate_solvable_solution` | `fn(&Ident, &Option<String>) -> TokenStream` | Generates SolvableSolution/Solvable/Analyzable impls |
140137
| `generate_shadow_support` | `fn(&ShadowConfig, &Ident) -> TokenStream` | Generates ShadowVariableSupport impl |
141-
| `generate_constraint_stream_extensions` | `fn(&Fields, &StandardVariableConfig, &Ident) -> TokenStream` | Generates `{Name}ConstraintStreams` trait + impl on ConstraintFactory; generates `{Entity}UnassignedFilter` trait if standard_variable_config is present |
138+
| `generate_constraint_stream_extensions` | `fn(&Fields, &Ident) -> TokenStream` | Generates `{Name}ConstraintStreams` trait + impl on ConstraintFactory |
142139
| `extract_option_inner_type` | `fn(&Type) -> Result<&Type, Error>` | Extracts `T` from `Option<T>` |
143140
| `extract_collection_inner_type` | `fn(&Type) -> Option<&Type>` | Extracts `T` from `Vec<T>` |
144141

@@ -164,17 +161,6 @@ struct ShadowConfig {
164161
}
165162
```
166163

167-
### `StandardVariableConfig`
168-
169-
```rust
170-
struct StandardVariableConfig {
171-
entity_collection: Option<String>,
172-
variable_field: Option<String>,
173-
variable_type: Option<String>,
174-
value_range: Option<String>,
175-
}
176-
```
177-
178164
## Architectural Notes
179165

180166
### Code Generation Targets

crates/solverforge-macros/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ pub fn derive_planning_entity(input: TokenStream) -> TokenStream {
115115
planning_score,
116116
value_range_provider,
117117
shadow_variable_updates,
118-
standard_variable_config,
119118
solverforge_constraints_path
120119
)
121120
)]

crates/solverforge-macros/src/planning_entity.rs

Lines changed: 103 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,86 @@ pub fn expand_derive(input: DeriveInput) -> Result<TokenStream, Error> {
245245
})
246246
.collect();
247247

248+
let optional_planning_variables: Vec<_> = planning_variables
249+
.iter()
250+
.filter_map(|field| {
251+
let field_name = field.ident.as_ref()?;
252+
field_option_inner_type(&field.ty).map(|field_type| (field_name, field_type))
253+
})
254+
.collect();
255+
256+
let unassigned_filter_extension = if optional_planning_variables.len() == 1 {
257+
let (field_name, field_type) = optional_planning_variables[0];
258+
let predicate_name = syn::Ident::new(
259+
&format!(
260+
"__{}_{}_unassigned",
261+
name.to_string().to_lowercase(),
262+
field_name
263+
),
264+
proc_macro2::Span::call_site(),
265+
);
266+
let filter_trait_name = syn::Ident::new(
267+
&format!("{}UnassignedFilter", name),
268+
proc_macro2::Span::call_site(),
269+
);
270+
271+
quote! {
272+
#[allow(non_snake_case)]
273+
fn #predicate_name<Solution>(
274+
_solution: &Solution,
275+
entity: &#name,
276+
) -> bool
277+
where
278+
Solution: ::solverforge::__internal::PlanningSolution,
279+
{
280+
let value: &::core::option::Option<#field_type> = &entity.#field_name;
281+
value.is_none()
282+
}
283+
284+
pub trait #filter_trait_name<Sc: ::solverforge::Score + 'static, Solution, E, F> {
285+
type Output;
286+
fn unassigned(self) -> Self::Output;
287+
}
288+
289+
impl<Sc, Solution, E, F> #filter_trait_name<Sc, Solution, E, F>
290+
for ::solverforge::__internal::UniConstraintStream<Solution, #name, E, F, Sc>
291+
where
292+
Sc: ::solverforge::Score + 'static,
293+
Solution: ::solverforge::__internal::PlanningSolution,
294+
E: Fn(&Solution) -> &[#name] + Send + Sync,
295+
F: ::solverforge::__internal::UniFilter<Solution, #name>,
296+
{
297+
type Output = ::solverforge::__internal::UniConstraintStream<
298+
Solution,
299+
#name,
300+
E,
301+
::solverforge::__internal::AndUniFilter<
302+
F,
303+
::solverforge::__internal::FnUniFilter<
304+
fn(&Solution, &#name) -> bool
305+
>,
306+
>,
307+
Sc,
308+
>;
309+
310+
fn unassigned(self) -> Self::Output {
311+
let (extractor, filter) = self.into_parts();
312+
::solverforge::__internal::UniConstraintStream::from_parts(
313+
extractor,
314+
::solverforge::__internal::AndUniFilter::new(
315+
filter,
316+
::solverforge::__internal::FnUniFilter::new(
317+
#predicate_name::<Solution> as fn(&Solution, &#name) -> bool
318+
),
319+
),
320+
)
321+
}
322+
}
323+
}
324+
} else {
325+
TokenStream::new()
326+
};
327+
248328
let list_variable_descriptors: Vec<_> = list_variables
249329
.iter()
250330
.map(|field| {
@@ -376,33 +456,40 @@ pub fn expand_derive(input: DeriveInput) -> Result<TokenStream, Error> {
376456
desc
377457
}
378458
}
459+
460+
#unassigned_filter_extension
379461
};
380462

381463
Ok(expanded)
382464
}
383465

384466
fn field_is_option_usize(ty: &syn::Type) -> bool {
467+
field_option_inner_type(ty)
468+
.and_then(|inner| {
469+
let syn::Type::Path(inner_path) = inner else {
470+
return None;
471+
};
472+
inner_path.path.segments.last()
473+
})
474+
.map(|segment| segment.ident == "usize")
475+
.unwrap_or(false)
476+
}
477+
478+
fn field_option_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
385479
let syn::Type::Path(type_path) = ty else {
386-
return false;
387-
};
388-
let Some(segment) = type_path.path.segments.last() else {
389-
return false;
480+
return None;
390481
};
482+
let segment = type_path.path.segments.last()?;
391483
if segment.ident != "Option" {
392-
return false;
484+
return None;
393485
}
394486
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
395-
return false;
487+
return None;
396488
};
397-
let Some(syn::GenericArgument::Type(syn::Type::Path(inner_path))) = args.args.first() else {
398-
return false;
489+
let Some(syn::GenericArgument::Type(inner)) = args.args.first() else {
490+
return None;
399491
};
400-
inner_path
401-
.path
402-
.segments
403-
.last()
404-
.map(|segment| segment.ident == "usize")
405-
.unwrap_or(false)
492+
Some(inner)
406493
}
407494

408495
#[cfg(test)]
@@ -431,5 +518,7 @@ mod tests {
431518
assert!(expanded.contains("with_value_range (\"workers\")"));
432519
assert!(expanded.contains("with_id_field (stringify ! (id))"));
433520
assert!(expanded.contains("pub fn entity_descriptor"));
521+
assert!(expanded.contains("pub trait TaskUnassignedFilter"));
522+
assert!(expanded.contains("fn unassigned (self)"));
434523
}
435524
}

0 commit comments

Comments
 (0)