diff --git a/docs/builtins.md b/docs/builtins.md index dddf124b9..324402597 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -31,7 +31,7 @@ In future, each builtin will be associated with a feature (many builtins could b | [x - y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-minus) | _ | | [x * y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-mul) | _ | | [numbers.range](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-numbersrange) | _ | - | [numbers.range_step](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-numbersrange_step) | _ | + | [numbers.range_step](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-numbersrange_step) | Supported. When `start > stop`, step is negated automatically to produce a descending range, matching OPA behavior. | | [x + y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-plus) | _ | | [rand.intn](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-randintn) | _ | | [x % y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-rem) | _ | diff --git a/src/builtins/numbers.rs b/src/builtins/numbers.rs index 2c55fcb7f..594ef3287 100644 --- a/src/builtins/numbers.rs +++ b/src/builtins/numbers.rs @@ -140,22 +140,24 @@ fn range_step(span: &Span, params: &[Ref], args: &[Value], strict: bool) - _ => (), } - if strict && (!incr.is_integer() || incr <= Number::from(0u64)) { - bail!(params[2].span().error("step must be a positive integer")) + if !incr.is_integer() || incr <= Number::from(0u64) { + if strict { + bail!(params[2].span().error("step must be a positive integer")) + } + return Ok(Value::Undefined); } - let (incr, num_elements) = match (v2.sub(&v1)?.as_i64(), incr.as_i64()) { - (Some(v), Some(incr)) if v >= 0 => (incr, v / incr + 1), - (Some(v), Some(incr)) => (-incr, -v / incr + 1), - _ => bail!(span.error("could not determine number of elements")), + let step = if v1 <= v2 { + incr + } else { + Number::from(0u64).sub(&incr)? }; - let mut values = Vec::with_capacity(num_elements as usize); - let incr = Number::from(incr); + let mut values = Vec::new(); let mut v = v1; - while (v <= v2 && incr.is_positive()) || (v >= v2 && !incr.is_positive()) { + while (v <= v2 && step.is_positive()) || (v >= v2 && !step.is_positive()) { values.push(Value::from(v.clone())); - v.add_assign(&incr)?; + v.add_assign(&step)?; // Guard vector growth as the stepped range accumulates. enforce_limit()?; } diff --git a/tests/interpreter/cases/builtins/numbers/range_step.yaml b/tests/interpreter/cases/builtins/numbers/range_step.yaml new file mode 100644 index 000000000..2642fbb08 --- /dev/null +++ b/tests/interpreter/cases/builtins/numbers/range_step.yaml @@ -0,0 +1,59 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +cases: + - note: ascending + data: {} + modules: + - | + package test + x = numbers.range_step(1, 5, 1) + query: data.test.x + want_result: [1, 2, 3, 4, 5] + + - note: step-2 + data: {} + modules: + - | + package test + x = numbers.range_step(0, 10, 2) + query: data.test.x + want_result: [0, 2, 4, 6, 8, 10] + + - note: descending + data: {} + modules: + - | + package test + x = numbers.range_step(5, 1, 1) + query: data.test.x + want_result: [5, 4, 3, 2, 1] + + - note: single-element + data: {} + modules: + - | + package test + x = numbers.range_step(3, 3, 1) + query: data.test.x + want_result: [3] + + - note: non-integer-step-undefined + strict: false + data: {} + modules: + - | + package test + x = numbers.range_step(1, 5, 0.5) + query: data.test.x + no_result: true + + - note: zero-step-undefined + strict: false + data: {} + modules: + - | + package test + x = numbers.range_step(1, 5, 0) + query: data.test.x + no_result: true