✨ feat(lang): support array operands for shift operators (<<, >>)#1319
✨ feat(lang): support array operands for shift operators (<<, >>)#1319
Conversation
harehare
commented
Feb 22, 2026
- Allow [array] << value to append value to array
- Allow value >> [array] to prepend value to array
- Update docs and tests for new operator behavior
- Allow [array] << value to append value to array - Allow value >> [array] to prepend value to array - Update docs and tests for new operator behavior
There was a problem hiding this comment.
Pull request overview
This PR adds support for using arrays as operands with shift operators (<< and >>), enabling convenient array manipulation. The feature allows [array] << value to append and value >> [array] to prepend, providing an intuitive syntax for common array operations.
Changes:
- Added array operand support for shift operators, where
<<appends to arrays and>>prepends to arrays - Improved error handling to use
Error::InvalidTypesinstead of silently returningNonefor invalid operand combinations - Added basic test coverage and updated documentation tables
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/books/src/reference/operators.md | Updated operator reference tables to document array behavior for shift operators |
| crates/mq-lang/src/eval/builtin.rs | Implemented array handling in SHIFT_LEFT and SHIFT_RIGHT builtins, improved error handling |
| crates/mq-lang/tests/integration_tests.rs | Added basic integration tests for array shift operations |
| crates/mq-repl/src/repl.rs | Fixed syntax highlighting pattern by removing non-existent -> operator |
| .github/codecov.yml | Added editors/zed to coverage exclusions |
Comments suppressed due to low confidence (1)
docs/books/src/reference/operators.md:101
- The documentation should include examples demonstrating the new array behavior for shift operators, similar to the examples provided for numbers, strings, and Markdown headings. Consider adding examples like:
# Append to array with left shift
[1, 2, 3] << 4
# => [1, 2, 3, 4]
# Prepend to array with right shift
0 >> [1, 2, 3]
# => [0, 1, 2, 3]
This would help users understand the new functionality more clearly.
#### Examples
```mq
# Bitwise right shift on numbers
4 >> 2
# => 1
shift_right(8, 2)
# => 2
# Remove characters from the end of a string
shift_right("hello", 2)
# => "hel"
"hello" >> 2
# => "hel"
# Demote a heading (increase depth)
let md = do to_markdown("# Heading 1") | first(); |
md >> 1
# => ## Heading 1
</details>
| #[case::shift_right_number("shift_right(4, 2)", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()),)] | ||
| #[case::shift_right_number_operator("4 >> 2", vec![RuntimeValue::None], Ok(vec![RuntimeValue::Number(1.into())].into()),)] | ||
| #[case::shift_left_array_operator("[1] << 2", vec![RuntimeValue::None], Ok(vec![vec![RuntimeValue::Number(1.into()), RuntimeValue::Number(2.into())].into()].into()),)] | ||
| #[case::shift_right_array_operator("2 >> [1]", vec![RuntimeValue::None], Ok(vec![vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(1.into())].into()].into()),)] |
There was a problem hiding this comment.
Consider adding test cases for edge cases:
- Appending/prepending to an empty array:
[] << 1and1 >> [] - Appending/prepending an array to an array:
[1, 2] << [3, 4]and[3, 4] >> [1, 2] - Appending/prepending None:
[1] << .and. >> [1]with None input
These edge cases would help ensure the implementation handles all scenarios correctly.
| #[case::shift_right_array_operator("2 >> [1]", vec![RuntimeValue::None], Ok(vec![vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(1.into())].into()].into()),)] | |
| #[case::shift_right_array_operator("2 >> [1]", vec![RuntimeValue::None], Ok(vec![vec![RuntimeValue::Number(2.into()), RuntimeValue::Number(1.into())].into()].into()),)] | |
| #[case::shift_left_array_empty_operator("[] << 1", | |
| vec![RuntimeValue::None], | |
| Ok(vec![vec![ | |
| RuntimeValue::Number(1.into()), | |
| ].into()].into()),)] | |
| #[case::shift_right_array_empty_operator("1 >> []", | |
| vec![RuntimeValue::None], | |
| Ok(vec![vec![ | |
| RuntimeValue::Number(1.into()), | |
| ].into()].into()),)] | |
| #[case::shift_left_array_to_array_operator("[1, 2] << [3, 4]", | |
| vec![RuntimeValue::None], | |
| Ok(vec![vec![ | |
| RuntimeValue::Number(1.into()), | |
| RuntimeValue::Number(2.into()), | |
| RuntimeValue::Number(3.into()), | |
| RuntimeValue::Number(4.into()), | |
| ].into()].into()),)] | |
| #[case::shift_right_array_to_array_operator("[3, 4] >> [1, 2]", | |
| vec![RuntimeValue::None], | |
| Ok(vec![vec![ | |
| RuntimeValue::Number(3.into()), | |
| RuntimeValue::Number(4.into()), | |
| RuntimeValue::Number(1.into()), | |
| RuntimeValue::Number(2.into()), | |
| ].into()].into()),)] | |
| #[case::shift_left_array_none_operator("[1] << .", | |
| vec![RuntimeValue::None], | |
| Ok(vec![vec![ | |
| RuntimeValue::Number(1.into()), | |
| RuntimeValue::None, | |
| ].into()].into()),)] | |
| #[case::shift_right_array_none_operator(". >> [1]", | |
| vec![RuntimeValue::None], | |
| Ok(vec![vec![ | |
| RuntimeValue::None, | |
| RuntimeValue::Number(1.into()), | |
| ].into()].into()),)] |
| constants::builtins::SHIFT_LEFT.to_string(), | ||
| vec![std::mem::take(a), std::mem::take(b)], | ||
| )), | ||
| _ => unreachable!(), |
There was a problem hiding this comment.
The change from returning RuntimeValue::NONE to Error::InvalidTypes for invalid type combinations is an improvement and makes the shift operators consistent with other builtin functions. However, this is a behavior change that could break existing code that relied on the previous silent failure behavior. Consider documenting this as a breaking change in the CHANGELOG if it hasn't been done already.
| _ => unreachable!(), | |
| _ => Err(Error::InvalidTypes( | |
| constants::builtins::SHIFT_LEFT.to_string(), | |
| Vec::new(), | |
| )), |