Skip to content

Commit 440d62d

Browse files
committed
test(macros): cover mixed shared tuple members
Add UI-pass coverage for shared tuples that mix shared terminals with opaque members. The new cases exercise a terminal borrowing a name before an opaque member moves it, an opaque empty tuple between shared terminals, and alias-style ConstraintSet return bounds. These examples lock in the compiler behavior that motivated the fix: authored tuple evaluation order is preserved and generic opaque members are constrained by the expected solution and score types.
1 parent 5f855fd commit 440d62d

1 file changed

Lines changed: 109 additions & 0 deletions

File tree

crates/solverforge-macros/tests/ui/pass/solverforge_constraints_other_member_forms.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use solverforge::prelude::*;
2+
use solverforge::prelude::ConstraintSet as CSet;
23
use solverforge::IncrementalConstraint;
34

45
struct Plan {
@@ -16,6 +17,13 @@ fn fixed_one() -> impl IncrementalConstraint<Plan, SoftScore> {
1617
.named("fixed one")
1718
}
1819

20+
fn fixed_named(name: String) -> impl ConstraintSet<Plan, SoftScore> {
21+
ConstraintFactory::<Plan, SoftScore>::new()
22+
.for_each(shifts as fn(&Plan) -> &[usize])
23+
.penalize(SoftScore::ONE)
24+
.named(name.as_str())
25+
}
26+
1927
fn fixed_constraints() -> impl ConstraintSet<Plan, SoftScore> {
2028
let fixed_two = ConstraintFactory::<Plan, SoftScore>::new()
2129
.for_each(shifts as fn(&Plan) -> &[usize])
@@ -29,6 +37,82 @@ fn fixed_constraints() -> impl ConstraintSet<Plan, SoftScore> {
2937
(fixed_two, fixed_three)
3038
}
3139

40+
#[solverforge_constraints]
41+
fn moved_name_constraints() -> impl ConstraintSet<Plan, SoftScore> {
42+
let by_employee = ConstraintFactory::<Plan, SoftScore>::new()
43+
.for_each(shifts as fn(&Plan) -> &[usize])
44+
.group_by(|employee_id: &usize| *employee_id, count());
45+
let name = String::from("moved fixed");
46+
47+
(
48+
by_employee
49+
.penalize(|_employee_id: &usize, count: &usize| SoftScore::of(*count as i64))
50+
.named(name.as_str()),
51+
fixed_named(name),
52+
by_employee
53+
.penalize(|_employee_id: &usize, count: &usize| {
54+
SoftScore::of((*count * *count) as i64)
55+
})
56+
.named("after move"),
57+
)
58+
}
59+
60+
#[solverforge_constraints]
61+
fn empty_tuple_constraints() -> impl ConstraintSet<Plan, SoftScore> {
62+
let by_employee = ConstraintFactory::<Plan, SoftScore>::new()
63+
.for_each(shifts as fn(&Plan) -> &[usize])
64+
.group_by(|employee_id: &usize| *employee_id, count());
65+
66+
(
67+
by_employee
68+
.penalize(|_employee_id: &usize, count: &usize| SoftScore::of(*count as i64))
69+
.named("empty linear"),
70+
(),
71+
by_employee
72+
.penalize(|_employee_id: &usize, count: &usize| {
73+
SoftScore::of((*count * *count) as i64)
74+
})
75+
.named("empty squared"),
76+
)
77+
}
78+
79+
#[solverforge_constraints]
80+
fn alias_terminal_constraints() -> impl CSet<Plan, SoftScore> {
81+
let by_employee = ConstraintFactory::<Plan, SoftScore>::new()
82+
.for_each(shifts as fn(&Plan) -> &[usize])
83+
.group_by(|employee_id: &usize| *employee_id, count());
84+
85+
(
86+
by_employee
87+
.penalize(|_employee_id: &usize, count: &usize| SoftScore::of(*count as i64))
88+
.named("alias linear"),
89+
by_employee
90+
.penalize(|_employee_id: &usize, count: &usize| {
91+
SoftScore::of((*count * *count) as i64)
92+
})
93+
.named("alias squared"),
94+
)
95+
}
96+
97+
#[solverforge_constraints]
98+
fn alias_empty_tuple_constraints() -> impl CSet<Plan, SoftScore> {
99+
let by_employee = ConstraintFactory::<Plan, SoftScore>::new()
100+
.for_each(shifts as fn(&Plan) -> &[usize])
101+
.group_by(|employee_id: &usize| *employee_id, count());
102+
103+
(
104+
by_employee
105+
.penalize(|_employee_id: &usize, count: &usize| SoftScore::of(*count as i64))
106+
.named("alias empty linear"),
107+
(),
108+
by_employee
109+
.penalize(|_employee_id: &usize, count: &usize| {
110+
SoftScore::of((*count * *count) as i64)
111+
})
112+
.named("alias empty squared"),
113+
)
114+
}
115+
32116
#[solverforge_constraints]
33117
fn call_single_constraints() -> impl ConstraintSet<Plan, SoftScore> {
34118
let by_employee = ConstraintFactory::<Plan, SoftScore>::new()
@@ -101,4 +185,29 @@ fn main() {
101185
assert_eq!(bound_metadata[0].name(), "bound linear");
102186
assert_eq!(bound_metadata[1].name(), "fixed duplicate");
103187
assert_eq!(bound_metadata[2].name(), "bound squared");
188+
189+
let moved_constraints = moved_name_constraints();
190+
let moved_results = moved_constraints.evaluate_each(&plan);
191+
assert_eq!(moved_results.len(), 3);
192+
assert_eq!(moved_results[0].name, "moved fixed");
193+
assert_eq!(moved_results[1].name, "moved fixed");
194+
assert_eq!(moved_results[2].name, "after move");
195+
196+
let empty_constraints = empty_tuple_constraints();
197+
let empty_results = empty_constraints.evaluate_each(&plan);
198+
assert_eq!(empty_results.len(), 2);
199+
assert_eq!(empty_results[0].name, "empty linear");
200+
assert_eq!(empty_results[1].name, "empty squared");
201+
202+
let alias_constraints = alias_terminal_constraints();
203+
let alias_results = alias_constraints.evaluate_each(&plan);
204+
assert_eq!(alias_results.len(), 2);
205+
assert_eq!(alias_results[0].name, "alias linear");
206+
assert_eq!(alias_results[1].name, "alias squared");
207+
208+
let alias_empty_constraints = alias_empty_tuple_constraints();
209+
let alias_empty_results = alias_empty_constraints.evaluate_each(&plan);
210+
assert_eq!(alias_empty_results.len(), 2);
211+
assert_eq!(alias_empty_results[0].name, "alias empty linear");
212+
assert_eq!(alias_empty_results[1].name, "alias empty squared");
104213
}

0 commit comments

Comments
 (0)