let Some(collection_expr) = parse_expression(&mut collection_ctx, collection_node)? else {
"Generator requires either a domain or collection (e.g. `{var_name} : domain` or `{var_name} <- expr`)"
let Some(collection_expr) = parse_expression(&mut collection_ctx, collection_node)? else {
/// Comprehension lettings are pure sugar. They cannot be left as declarations for the rewriter:
/// their value usually mentions the generator variables, which comprehension expansion substitutes
fn substitute_lettings(expr: Expression, lettings: &[(DeclarationPtr, Expression)]) -> Expression {
// If we're in a set context, add error and return early since comprehensions don't produce sets
// Use the return expression symbol table which already has quantified variables (as Given) and parent as parent
let comprehension = builder.with_return_value(substitute_lettings(return_expr, &lettings));
let operator_str = &ctx.source_code[operator_node.start_byte()..operator_node.end_byte()];
let mut comprehension = builder.with_return_value(substitute_lettings(expression, &lettings));
let wrapped_comprehension = Expression::Comprehension(Metadata::new(), Moo::new(comprehension));