-
Notifications
You must be signed in to change notification settings - Fork 54
Add lambda expression and map() function with ARM syntax
#1238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add lambda expression and map() function with ARM syntax
#1238
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements lambda expressions and the map() function for DSC configuration documents, using ARM-compatible syntax (lambda('param', body) and lambdaVariables('param')). The implementation stores lambdas in the context with unique UUID-based identifiers and evaluates them by binding lambda parameters to array elements.
Key changes:
- Added
Lambdastruct and lambda-specific handling in the function evaluation pipeline - Implemented
map(),lambda(), andlambdaVariables()functions for array transformations - Extended
Contextwith lambda storage (lambdas) and lambda parameter bindings (lambda_variables)
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/dsc-lib/src/parser/functions.rs | Added Lambda struct and FunctionArg::Lambda variant; special handling for lambda() function to bypass normal evaluation |
| lib/dsc-lib/src/functions/mod.rs | Implemented invoke_lambda() to create and store lambda objects with UUID-based IDs; registered new lambda-related functions |
| lib/dsc-lib/src/functions/map.rs | Implemented array transformation function that evaluates lambdas for each element with optional index parameter |
| lib/dsc-lib/src/functions/lambda_variables.rs | Implemented parameter lookup function for accessing lambda-bound variables during evaluation |
| lib/dsc-lib/src/functions/lambda.rs | Created lambda function stub that returns error if invoked directly (handled specially elsewhere) |
| lib/dsc-lib/src/configure/context.rs | Added lambda_variables HashMap and lambdas RefCell for lambda storage and parameter binding |
| lib/dsc-lib/locales/en-us.toml | Added localized error messages and descriptions for lambda-related functions |
| dsc/tests/dsc_lambda.tests.ps1 | Added integration tests for map() with various lambda scenarios (multiplication, index usage, range, empty array) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub extensions: Vec<DscExtension>, | ||
| pub lambda_variables: HashMap<String, Value>, |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new fields lambda_variables and lambdas lack documentation comments explaining their purpose and usage. Add doc comments to clarify:
lambda_variables: Storage for lambda parameter bindings during lambda body evaluationlambdas: Registry of lambda functions by their unique ID
| pub extensions: Vec<DscExtension>, | |
| pub lambda_variables: HashMap<String, Value>, | |
| pub extensions: Vec<DscExtension>, | |
| /// Storage for lambda parameter bindings during lambda body evaluation | |
| pub lambda_variables: HashMap<String, Value>, | |
| /// Registry of lambda functions by their unique ID |
| pub execution_type: ExecutionKind, | ||
| pub extensions: Vec<DscExtension>, | ||
| pub lambda_variables: HashMap<String, Value>, | ||
| pub lambdas: std::cell::RefCell<HashMap<String, crate::parser::functions::Lambda>>, |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using RefCell<HashMap> for lambdas in a Clone-able Context has implications for how lambda storage works across context clones. When context.clone() is called (e.g., in map.rs line 64), the RefCell is cloned which creates a new RefCell with a new HashMap that is a copy of the original. This means:
- Lambdas created in the cloned context won't be visible in the original context
- The original lambdas are duplicated, which may not be the intended behavior
If lambdas should be shared across context clones, use Rc<RefCell<HashMap<String, Lambda>>> instead. If lambdas should be scoped to each context clone (current behavior), document this design decision with a comment.
| Lambda(Lambda), | ||
| } | ||
|
|
||
| #[derive(Clone)] |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Lambda struct should derive Debug for consistency with other types in the codebase and to aid debugging. Since Expression is Clone, Lambda can also derive Debug without issues.
| #[derive(Clone)] | |
| #[derive(Clone, Debug)] |
| Lambda(Lambda), | ||
| } | ||
|
|
||
| #[derive(Clone)] | ||
| pub struct Lambda { | ||
| pub parameters: Vec<String>, | ||
| pub body: Expression, | ||
| } | ||
|
|
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FunctionArg::Lambda variant is never constructed - it's only matched to return an error. This suggests it's dead code. Consider removing this variant since lambda functions are handled specially via the string ID mechanism, not as a direct FunctionArg type.
| Lambda(Lambda), | |
| } | |
| #[derive(Clone)] | |
| pub struct Lambda { | |
| pub parameters: Vec<String>, | |
| pub body: Expression, | |
| } | |
| } |
| // Return the ID as a string value | ||
| Ok(Value::String(lambda_id)) | ||
| } | ||
|
|
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lambdas created during expression evaluation are stored in context.lambdas but are never cleaned up or removed. This can lead to a memory leak in configurations that create many lambda expressions (e.g., in loops or repeated evaluations). Consider implementing a cleanup mechanism or using a different scoping strategy for lambda lifetime management.
| /// Removes a lambda from the context by its ID. | |
| /// Call this after you are done using a lambda to avoid memory leaks. | |
| pub fn remove_lambda(context: &Context, lambda_id: &str) { | |
| context.lambdas.borrow_mut().remove(lambda_id); | |
| } |
| let lambdas = context.lambdas.borrow(); | ||
| let Some(lambda) = lambdas.get(lambda_id) else { | ||
| return Err(DscError::Parser(t!("functions.map.lambdaNotFound", id = lambda_id).to_string())); | ||
| }; | ||
|
|
||
| // Validate parameter count (1 or 2 parameters) | ||
| if lambda.parameters.is_empty() || lambda.parameters.len() > 2 { | ||
| return Err(DscError::Parser(t!("functions.map.lambdaMustHave1Or2Params").to_string())); | ||
| } | ||
|
|
||
| // Create function dispatcher for evaluating lambda body | ||
| let dispatcher = FunctionDispatcher::new(); | ||
| let mut result_array = Vec::new(); | ||
|
|
||
| // Iterate through array and evaluate lambda for each element | ||
| for (index, element) in array.iter().enumerate() { | ||
| // Create a new context with lambda variables bound | ||
| let mut lambda_context = context.clone(); |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The lambda is borrowed from context.lambdas and then cloned on line 64, but the borrow from line 47 is not explicitly dropped before the clone. While Rust's borrow checker allows this because the borrow is only used to get the lambda reference and doesn't extend past line 55, it would be clearer to explicitly drop the borrow before cloning the context. Consider restructuring as:
let lambda = {
let lambdas = context.lambdas.borrow();
lambdas.get(lambda_id)
.ok_or_else(|| DscError::Parser(t!("functions.map.lambdaNotFound", id = lambda_id).to_string()))?
.clone()
};
// ... validate parameter count ...
// Then use lambda directly without holding the borrowThis makes it clearer that we're cloning the lambda value and releasing the borrow before cloning the context.
| '@ | ||
| $out = $config_yaml | dsc config get -f - | ConvertFrom-Json | ||
| $LASTEXITCODE | Should -Be 0 | ||
| $out.results[0].result.actualState.output | Should -Be $null |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test expects $null for an empty array output, but this may not be the correct assertion. An empty array @() is different from $null in PowerShell. Verify that DSC actually returns $null for empty arrays, or update the assertion to check for an empty array: $out.results[0].result.actualState.output.Count | Should -Be 0.
| $out.results[0].result.actualState.output | Should -Be $null | |
| $out.results[0].result.actualState.output.Count | Should -Be 0 |
| Lambda(Lambda), | ||
| } | ||
|
|
||
| #[derive(Clone)] |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing documentation for the Lambda struct. Since this is a public struct (indicated by pub), it should have a doc comment explaining its purpose, fields, and how it's used in the lambda expression system.
| #[derive(Clone)] | |
| #[derive(Clone)] | |
| /// Represents a lambda expression in the lambda expression system. | |
| /// | |
| /// The `Lambda` struct encapsulates a set of parameter names and an expression body. | |
| /// It is used to model anonymous functions or closures that can be passed as arguments | |
| /// or evaluated within the expression system. | |
| /// | |
| /// # Fields | |
| /// - `parameters`: The names of the parameters that the lambda accepts. | |
| /// - `body`: The expression that forms the body of the lambda. |
PR Summary
This PR implements lambda expressions and the
map()function for DSC configuration documents, enabling array transformations. The implementation uses ARM compatible syntax e.g., (lambda('param', body)andlambdaVariables('param'))Example usage:
Seperated tests in a different file.
PR Context
Partially addresses #57.