solverforge 0.2.5__tar.gz → 0.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {solverforge-0.2.5 → solverforge-0.2.6}/Cargo.lock +5 -5
- {solverforge-0.2.5 → solverforge-0.2.6}/Cargo.toml +1 -1
- {solverforge-0.2.5 → solverforge-0.2.6}/PKG-INFO +1 -1
- {solverforge-0.2.5 → solverforge-0.2.6}/pyproject.toml +1 -1
- {solverforge-0.2.5 → solverforge-0.2.6}/python/solverforge/__init__.py +4 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/python/solverforge/__init__.py +4 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/collectors.rs +89 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/lambda_analyzer.rs +97 -1
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/score.rs +687 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/Cargo.toml +1 -1
- {solverforge-0.2.5 → solverforge-0.2.6}/README.md +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/python/solverforge/solver/__init__.py +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/python/solverforge/solver/config.py +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/python/solverforge/solver/domain.py +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/python/solverforge/solver/score.py +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/python/solverforge/test/__init__.py +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/Cargo.toml +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/README.md +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/analysis/explanation.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/analysis/manager.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/analysis/mod.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/bridge.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/constraints/collectors.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/constraints/constraint.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/constraints/joiners.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/constraints/mod.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/constraints/stream.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/domain/annotations.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/domain/class.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/domain/constraint_config.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/domain/mod.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/domain/model.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/domain/shadow.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/error.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/handles.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/lib.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/score/bendable.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/score/hard_soft.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/score/hard_soft_decimal.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/score/mod.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/score/simple.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/score/simple_decimal.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/solver/builder.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/solver/client.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/solver/config.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/solver/environment.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/solver/factory.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/solver/mod.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/solver/request.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/solver/response.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/solver/termination.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/traits.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/value.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/wasm/expr_builder.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/wasm/expression.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/wasm/generator.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/wasm/host_functions.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/wasm/memory.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-core/src/wasm/mod.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/Cargo.toml +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/README.md +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/python/solverforge/solver/__init__.py +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/python/solverforge/solver/config.py +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/python/solverforge/solver/domain.py +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/python/solverforge/solver/score.py +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/python/solverforge/test/__init__.py +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/analysis.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/annotations.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/bridge.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/decorators.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/joiners.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/lib.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/service.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/solver.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-python/src/stream.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/README.md +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/src/config.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/src/error.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/src/jar.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/src/lib.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/src/service.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/src/util.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/tests/employee_scheduling_integration.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/tests/integration.rs +0 -0
- {solverforge-0.2.5 → solverforge-0.2.6}/solverforge-service/tests/solve_integration.rs +0 -0
|
@@ -1507,7 +1507,7 @@ dependencies = [
|
|
|
1507
1507
|
|
|
1508
1508
|
[[package]]
|
|
1509
1509
|
name = "solverforge"
|
|
1510
|
-
version = "0.2.
|
|
1510
|
+
version = "0.2.6"
|
|
1511
1511
|
dependencies = [
|
|
1512
1512
|
"solverforge-core",
|
|
1513
1513
|
"solverforge-derive",
|
|
@@ -1516,7 +1516,7 @@ dependencies = [
|
|
|
1516
1516
|
|
|
1517
1517
|
[[package]]
|
|
1518
1518
|
name = "solverforge-core"
|
|
1519
|
-
version = "0.2.
|
|
1519
|
+
version = "0.2.6"
|
|
1520
1520
|
dependencies = [
|
|
1521
1521
|
"base64",
|
|
1522
1522
|
"indexmap",
|
|
@@ -1532,7 +1532,7 @@ dependencies = [
|
|
|
1532
1532
|
|
|
1533
1533
|
[[package]]
|
|
1534
1534
|
name = "solverforge-derive"
|
|
1535
|
-
version = "0.2.
|
|
1535
|
+
version = "0.2.6"
|
|
1536
1536
|
dependencies = [
|
|
1537
1537
|
"proc-macro2",
|
|
1538
1538
|
"quote",
|
|
@@ -1544,7 +1544,7 @@ dependencies = [
|
|
|
1544
1544
|
|
|
1545
1545
|
[[package]]
|
|
1546
1546
|
name = "solverforge-python"
|
|
1547
|
-
version = "0.2.
|
|
1547
|
+
version = "0.2.6"
|
|
1548
1548
|
dependencies = [
|
|
1549
1549
|
"log",
|
|
1550
1550
|
"pyo3",
|
|
@@ -1559,7 +1559,7 @@ dependencies = [
|
|
|
1559
1559
|
|
|
1560
1560
|
[[package]]
|
|
1561
1561
|
name = "solverforge-service"
|
|
1562
|
-
version = "0.2.
|
|
1562
|
+
version = "0.2.6"
|
|
1563
1563
|
dependencies = [
|
|
1564
1564
|
"base64",
|
|
1565
1565
|
"chrono",
|
|
@@ -33,6 +33,8 @@ from solverforge._solverforge import (
|
|
|
33
33
|
HardMediumSoftScore,
|
|
34
34
|
HardSoftDecimalScore,
|
|
35
35
|
HardMediumSoftDecimalScore,
|
|
36
|
+
BendableScore,
|
|
37
|
+
BendableDecimalScore,
|
|
36
38
|
# Decorators
|
|
37
39
|
planning_entity,
|
|
38
40
|
planning_solution,
|
|
@@ -105,6 +107,8 @@ __all__ = [
|
|
|
105
107
|
"HardMediumSoftScore",
|
|
106
108
|
"HardSoftDecimalScore",
|
|
107
109
|
"HardMediumSoftDecimalScore",
|
|
110
|
+
"BendableScore",
|
|
111
|
+
"BendableDecimalScore",
|
|
108
112
|
# Decorators
|
|
109
113
|
"planning_entity",
|
|
110
114
|
"planning_solution",
|
|
@@ -33,6 +33,8 @@ from solverforge._solverforge import (
|
|
|
33
33
|
HardMediumSoftScore,
|
|
34
34
|
HardSoftDecimalScore,
|
|
35
35
|
HardMediumSoftDecimalScore,
|
|
36
|
+
BendableScore,
|
|
37
|
+
BendableDecimalScore,
|
|
36
38
|
# Decorators
|
|
37
39
|
planning_entity,
|
|
38
40
|
planning_solution,
|
|
@@ -105,6 +107,8 @@ __all__ = [
|
|
|
105
107
|
"HardMediumSoftScore",
|
|
106
108
|
"HardSoftDecimalScore",
|
|
107
109
|
"HardMediumSoftDecimalScore",
|
|
110
|
+
"BendableScore",
|
|
111
|
+
"BendableDecimalScore",
|
|
108
112
|
# Decorators
|
|
109
113
|
"planning_entity",
|
|
110
114
|
"planning_solution",
|
|
@@ -244,6 +244,57 @@ impl PyConstraintCollectors {
|
|
|
244
244
|
})
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
/// Compose multiple collectors into a single result.
|
|
248
|
+
///
|
|
249
|
+
/// # Arguments
|
|
250
|
+
/// * `collectors` - List of collectors to compose
|
|
251
|
+
/// * `combiner` - Lambda that combines the results into a single value
|
|
252
|
+
///
|
|
253
|
+
/// # Example
|
|
254
|
+
/// ```python
|
|
255
|
+
/// ConstraintCollectors.compose(
|
|
256
|
+
/// [ConstraintCollectors.count(), ConstraintCollectors.sum(lambda x: x.value)],
|
|
257
|
+
/// lambda count, total: total / count if count > 0 else 0
|
|
258
|
+
/// )
|
|
259
|
+
/// ```
|
|
260
|
+
#[staticmethod]
|
|
261
|
+
fn compose(
|
|
262
|
+
py: Python<'_>,
|
|
263
|
+
collectors: Vec<PyCollector>,
|
|
264
|
+
combiner: Py<PyAny>,
|
|
265
|
+
) -> PyResult<PyCollector> {
|
|
266
|
+
let rust_collectors: Vec<Collector> = collectors.iter().map(|c| c.to_rust()).collect();
|
|
267
|
+
let combiner_info = LambdaInfo::new(py, combiner, "compose_combiner")?;
|
|
268
|
+
Ok(PyCollector {
|
|
269
|
+
inner: Collector::compose(rust_collectors, combiner_info.to_wasm_function()),
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// Apply a collector conditionally based on a predicate.
|
|
274
|
+
///
|
|
275
|
+
/// # Arguments
|
|
276
|
+
/// * `predicate` - Lambda that returns true if the item should be collected
|
|
277
|
+
/// * `collector` - The collector to apply when predicate is true
|
|
278
|
+
///
|
|
279
|
+
/// # Example
|
|
280
|
+
/// ```python
|
|
281
|
+
/// ConstraintCollectors.conditionally(
|
|
282
|
+
/// lambda shift: shift.is_overtime,
|
|
283
|
+
/// ConstraintCollectors.count()
|
|
284
|
+
/// )
|
|
285
|
+
/// ```
|
|
286
|
+
#[staticmethod]
|
|
287
|
+
fn conditionally(
|
|
288
|
+
py: Python<'_>,
|
|
289
|
+
predicate: Py<PyAny>,
|
|
290
|
+
collector: PyCollector,
|
|
291
|
+
) -> PyResult<PyCollector> {
|
|
292
|
+
let pred_info = LambdaInfo::new(py, predicate, "conditionally_pred")?;
|
|
293
|
+
Ok(PyCollector {
|
|
294
|
+
inner: Collector::conditionally(pred_info.to_wasm_function(), collector.to_rust()),
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
247
298
|
fn __repr__(&self) -> &'static str {
|
|
248
299
|
"ConstraintCollectors()"
|
|
249
300
|
}
|
|
@@ -439,4 +490,42 @@ mod tests {
|
|
|
439
490
|
let repr = collector.__repr__();
|
|
440
491
|
assert!(repr.contains("Count"));
|
|
441
492
|
}
|
|
493
|
+
|
|
494
|
+
#[test]
|
|
495
|
+
fn test_compose_collector() {
|
|
496
|
+
init_python();
|
|
497
|
+
Python::with_gil(|py| {
|
|
498
|
+
let locals = PyDict::new(py);
|
|
499
|
+
py.run(c"combiner = lambda a, b: a + b", None, Some(&locals))
|
|
500
|
+
.unwrap();
|
|
501
|
+
let combiner = locals.get_item("combiner").unwrap().unwrap();
|
|
502
|
+
|
|
503
|
+
let collectors = vec![
|
|
504
|
+
PyConstraintCollectors::count(),
|
|
505
|
+
PyConstraintCollectors::count_distinct(),
|
|
506
|
+
];
|
|
507
|
+
|
|
508
|
+
let collector =
|
|
509
|
+
PyConstraintCollectors::compose(py, collectors, combiner.unbind()).unwrap();
|
|
510
|
+
let rust_collector = collector.to_rust();
|
|
511
|
+
assert!(matches!(rust_collector, Collector::Compose { .. }));
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
#[test]
|
|
516
|
+
fn test_conditionally_collector() {
|
|
517
|
+
init_python();
|
|
518
|
+
Python::with_gil(|py| {
|
|
519
|
+
let locals = PyDict::new(py);
|
|
520
|
+
py.run(c"pred = lambda x: x.active", None, Some(&locals))
|
|
521
|
+
.unwrap();
|
|
522
|
+
let pred = locals.get_item("pred").unwrap().unwrap();
|
|
523
|
+
|
|
524
|
+
let inner = PyConstraintCollectors::count();
|
|
525
|
+
let collector =
|
|
526
|
+
PyConstraintCollectors::conditionally(py, pred.unbind(), inner).unwrap();
|
|
527
|
+
let rust_collector = collector.to_rust();
|
|
528
|
+
assert!(matches!(rust_collector, Collector::Conditionally { .. }));
|
|
529
|
+
});
|
|
530
|
+
}
|
|
442
531
|
}
|
|
@@ -172,7 +172,7 @@ fn analyze_lambda_bytecode(py: Python<'_>, lambda_info: &LambdaInfo) -> PyResult
|
|
|
172
172
|
"RESUME" | "PRECALL" | "PUSH_NULL" | "COPY_FREE_VARS" | "CACHE" => {
|
|
173
173
|
// Skip these opcodes
|
|
174
174
|
}
|
|
175
|
-
"LOAD_FAST" | "LOAD_FAST_CHECK" => {
|
|
175
|
+
"LOAD_FAST" | "LOAD_FAST_CHECK" | "LOAD_FAST_AND_CLEAR" => {
|
|
176
176
|
// Load a local variable (parameter) - argval is the variable name
|
|
177
177
|
// We need to find the parameter index
|
|
178
178
|
let var_name: String = argval.extract()?;
|
|
@@ -180,6 +180,29 @@ fn analyze_lambda_bytecode(py: Python<'_>, lambda_info: &LambdaInfo) -> PyResult
|
|
|
180
180
|
let varnames: Vec<String> = code.getattr("co_varnames")?.extract()?;
|
|
181
181
|
if let Some(idx) = varnames.iter().position(|n| n == &var_name) {
|
|
182
182
|
stack.push(BytecodeValue::Param(idx as u32));
|
|
183
|
+
} else {
|
|
184
|
+
// Variable not in varnames - could be a closure variable
|
|
185
|
+
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
|
186
|
+
"Cannot analyze lambda: unknown variable '{}'. Lambda parameters must be used directly.",
|
|
187
|
+
var_name
|
|
188
|
+
)));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
"LOAD_FAST_LOAD_FAST" => {
|
|
192
|
+
// Python 3.12+ optimization: loads two variables at once
|
|
193
|
+
// argval is a tuple of two variable names like ('a', 'b')
|
|
194
|
+
let var_names: Vec<String> = argval.extract()?;
|
|
195
|
+
let code = callable.getattr("__code__")?;
|
|
196
|
+
let varnames: Vec<String> = code.getattr("co_varnames")?.extract()?;
|
|
197
|
+
for var_name in var_names {
|
|
198
|
+
if let Some(idx) = varnames.iter().position(|n| n == &var_name) {
|
|
199
|
+
stack.push(BytecodeValue::Param(idx as u32));
|
|
200
|
+
} else {
|
|
201
|
+
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
|
202
|
+
"Cannot analyze lambda: unknown variable '{}'. Lambda parameters must be used directly.",
|
|
203
|
+
var_name
|
|
204
|
+
)));
|
|
205
|
+
}
|
|
183
206
|
}
|
|
184
207
|
}
|
|
185
208
|
"LOAD_ATTR" | "LOAD_METHOD" => {
|
|
@@ -1361,6 +1384,79 @@ mod tests {
|
|
|
1361
1384
|
});
|
|
1362
1385
|
}
|
|
1363
1386
|
|
|
1387
|
+
#[test]
|
|
1388
|
+
fn test_analyze_bi_lambda_direct_param_add() {
|
|
1389
|
+
// Tests LOAD_FAST_LOAD_FAST bytecode (Python 3.12+)
|
|
1390
|
+
// This is used by compose() combiner lambdas like: lambda a, b: a + b
|
|
1391
|
+
init_python();
|
|
1392
|
+
Python::attach(|py| {
|
|
1393
|
+
let locals = PyDict::new(py);
|
|
1394
|
+
py.run(c"f = lambda a, b: a + b", None, Some(&locals))
|
|
1395
|
+
.unwrap();
|
|
1396
|
+
let func = locals.get_item("f").unwrap().unwrap();
|
|
1397
|
+
|
|
1398
|
+
let info = LambdaInfo {
|
|
1399
|
+
callable: func.unbind(),
|
|
1400
|
+
name: "test".to_string(),
|
|
1401
|
+
param_count: 2,
|
|
1402
|
+
class_hint: None,
|
|
1403
|
+
expression: None,
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1406
|
+
let result = analyze_lambda(py, &info).unwrap();
|
|
1407
|
+
|
|
1408
|
+
match result {
|
|
1409
|
+
Expression::Add { left, right } => {
|
|
1410
|
+
assert!(matches!(*left, Expression::Param { index: 0 }));
|
|
1411
|
+
assert!(matches!(*right, Expression::Param { index: 1 }));
|
|
1412
|
+
}
|
|
1413
|
+
_ => panic!("Expected Add expression, got {:?}", result),
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
#[test]
|
|
1419
|
+
fn test_analyze_tri_lambda_arithmetic() {
|
|
1420
|
+
// Tests three-parameter lambda with arithmetic
|
|
1421
|
+
init_python();
|
|
1422
|
+
Python::attach(|py| {
|
|
1423
|
+
let locals = PyDict::new(py);
|
|
1424
|
+
py.run(c"f = lambda a, b, c: a + b + c", None, Some(&locals))
|
|
1425
|
+
.unwrap();
|
|
1426
|
+
let func = locals.get_item("f").unwrap().unwrap();
|
|
1427
|
+
|
|
1428
|
+
let info = LambdaInfo {
|
|
1429
|
+
callable: func.unbind(),
|
|
1430
|
+
name: "test".to_string(),
|
|
1431
|
+
param_count: 3,
|
|
1432
|
+
class_hint: None,
|
|
1433
|
+
expression: None,
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
let result = analyze_lambda(py, &info).unwrap();
|
|
1437
|
+
|
|
1438
|
+
// Should be Add(Add(a, b), c)
|
|
1439
|
+
match result {
|
|
1440
|
+
Expression::Add { left, right } => {
|
|
1441
|
+
// right should be Param 2 (c)
|
|
1442
|
+
assert!(matches!(*right, Expression::Param { index: 2 }));
|
|
1443
|
+
// left should be Add(a, b)
|
|
1444
|
+
match *left {
|
|
1445
|
+
Expression::Add {
|
|
1446
|
+
left: l2,
|
|
1447
|
+
right: r2,
|
|
1448
|
+
} => {
|
|
1449
|
+
assert!(matches!(*l2, Expression::Param { index: 0 }));
|
|
1450
|
+
assert!(matches!(*r2, Expression::Param { index: 1 }));
|
|
1451
|
+
}
|
|
1452
|
+
_ => panic!("Expected nested Add"),
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
_ => panic!("Expected Add expression, got {:?}", result),
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1364
1460
|
#[test]
|
|
1365
1461
|
fn test_analyze_nested_field_access() {
|
|
1366
1462
|
init_python();
|