Skip to content

Commit 3cff6c5

Browse files
authored
refactor(value): migrate Array storage abstraction (#785)
* feat(value): introduce Array storage abstraction Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> docs(value): document Array storage abstraction Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9ac07e53-88fc-4050-bd56-c0d9761b911b * refactor(value): migrate Array storage abstraction Wire Value::Array to the opaque Array wrapper and update all call sites to use explicit APIs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9ac07e53-88fc-4050-bd56-c0d9761b911b --------- Copilot-Session: 9ac07e53-88fc-4050-bd56-c0d9761b911b
1 parent e60e707 commit 3cff6c5

21 files changed

Lines changed: 486 additions & 36 deletions

File tree

bindings/wasm/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,8 @@ mod tests {
523523
assert_eq!(
524524
r["files"][0]["covered"]
525525
.as_array()
526-
.map_err(crate::error_to_jsvalue)?,
526+
.map_err(crate::error_to_jsvalue)?
527+
.as_slice(),
527528
&vec![regorus::Value::from(3)]
528529
);
529530

docs/value/array.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Array
2+
3+
Opaque container for `Value::Array`'s ordered element storage, enabling
4+
alternative backends without call-site changes. It follows the same
5+
abstraction pattern as [`Object`](object.md) and [`Set`](set.md).
6+
7+
## Design
8+
9+
`Array` wraps a `Vec<Value>` today but exposes only a curated method surface
10+
(`get`, `get_mut`, `first`, `last`, `contains`, `iter`, `iter_mut`, `push`,
11+
`append`, `extend`, `extend_from_slice`, `retain`, `clear`, `reverse`, `sort`,
12+
`cursor`, and serde). The inner vector is private, and `Array` does not
13+
implement `Deref`, so callers cannot depend on the backing representation.
14+
15+
Indexing uses `Index<usize>` and returns `Value::Undefined` for an out-of-range
16+
index, matching `Value` indexing semantics. Use `get` when distinguishing a
17+
missing element from an element whose value is explicitly `Undefined`.
18+
19+
Iteration follows sequence order. The opaque cursor supports incremental
20+
traversal needed by RVM iteration state without exposing iterator internals.
21+
`Ord` is implemented against the sequence iterator so alternative backends can
22+
preserve the current array comparison behavior.
23+
24+
## Scenarios enabled
25+
26+
- **Inline-small storage** — store short arrays inline and spill to the heap
27+
only for larger values.
28+
- **Lazy/streaming storage** — materialize elements from JSON, CBOR, or a host
29+
provider on demand.
30+
- **Arena allocation** — use bump allocation for evaluation-time temporaries
31+
and release them together at query end.
32+
- **FFI-backed storage** — access host-language lists or arrays without
33+
copying at every binding boundary.

src/builtins/arrays.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,6 @@ fn slice(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
6565
return Ok(Value::new_array());
6666
}
6767

68-
let slice = &array[start..stop];
68+
let slice = array.as_slice().get(start..stop).unwrap_or_default();
6969
Ok(Value::from(slice.to_vec()))
7070
}

src/builtins/azure_policy/template_functions_collection.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ fn fn_intersection(
5959
match *first {
6060
Value::Array(ref first) => {
6161
// Intersection of arrays: keep elements from first that appear in all others.
62-
let mut result: Vec<Value> = first.as_ref().clone();
62+
let mut result: Vec<Value> = first.as_slice().to_vec();
6363
for arg in rest {
6464
let Value::Array(ref other) = *arg else {
6565
return Ok(Value::Undefined);
@@ -149,7 +149,9 @@ fn fn_take(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -
149149
match *original {
150150
Value::Array(ref arr) => {
151151
let n = count.min(arr.len());
152-
Ok(Value::from(arr.get(..n).unwrap_or_default().to_vec()))
152+
Ok(Value::from(
153+
arr.as_slice().get(..n).unwrap_or_default().to_vec(),
154+
))
153155
}
154156
Value::String(ref s) => {
155157
let taken: alloc::string::String = s.chars().take(count).collect();
@@ -172,7 +174,9 @@ fn fn_skip(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -
172174
match *original {
173175
Value::Array(ref arr) => {
174176
let n = count.min(arr.len());
175-
Ok(Value::from(arr.get(n..).unwrap_or_default().to_vec()))
177+
Ok(Value::from(
178+
arr.as_slice().get(n..).unwrap_or_default().to_vec(),
179+
))
176180
}
177181
Value::String(ref s) => {
178182
let skipped: alloc::string::String = s.chars().skip(count).collect();

src/builtins/azure_policy/template_functions_misc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ fn fn_items(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool)
9090
entry.insert(Value::from("value"), v.clone());
9191
result.push(Value::Object(Rc::new(entry)));
9292
}
93-
Ok(Value::Array(Rc::new(result)))
93+
Ok(Value::from_array(result))
9494
}
9595

9696
// ── indexFromEnd ──────────────────────────────────────────────────────

src/builtins/json_patch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ impl EditNode {
7979
array.push(value.render()?);
8080
enforce_limit()?;
8181
}
82-
Value::Array(crate::Rc::new(array))
82+
Value::Array(crate::Rc::new(crate::value::Array::from(array)))
8383
}
8484
Self::Set(members) => {
8585
let mut set = BTreeSet::new();

src/builtins/net.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ fn _cidr_expand(cidr: Arc<str>) -> Result<Value> {
153153
enforce_limit()?;
154154
}
155155

156-
Ok(Value::Array(Arc::from(hosts)))
156+
Ok(Value::from_array(hosts))
157157
}
158158

159159
#[cfg(test)]

src/builtins/objects.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,10 @@ fn is_subset(sup: &Value, sub: &Value) -> bool {
354354
})
355355
}
356356
(Value::Set(sup), Value::Set(sub)) => sub.is_subset(sup),
357-
(Value::Array(sup), Value::Array(sub)) => sup.windows(sub.len()).any(|w| w == &sub[..]),
357+
(Value::Array(sup), Value::Array(sub)) => sup
358+
.as_slice()
359+
.windows(sub.len())
360+
.any(|w| w == sub.as_slice()),
358361
(Value::Array(sup), Value::Set(_)) => {
359362
let sup = Value::from_set(sup.iter().cloned().collect());
360363
is_subset(&sup, sub)
@@ -504,7 +507,7 @@ fn json_patch(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)
504507

505508
let ops = args[1].as_array()?;
506509

507-
let patched = super::json_patch::apply(&args[0], ops);
510+
let patched = super::json_patch::apply(&args[0], ops.as_slice());
508511
match patched {
509512
Ok(patched) => Ok(patched),
510513
// Resource-limit errors must propagate rather than look like an

src/builtins/utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use crate::ast::{Expr, Ref};
66
use crate::lexer::Span;
77
use crate::number::Number;
8-
use crate::value::{Object, Set};
8+
use crate::value::{Array, Object, Set};
99
use crate::Rc;
1010
use crate::Value;
1111
use crate::*;
@@ -147,7 +147,7 @@ pub fn ensure_string_collection<'a>(fcn: &str, arg: &Expr, v: &'a Value) -> Resu
147147
Ok(collection)
148148
}
149149

150-
pub fn ensure_array(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<Vec<Value>>> {
150+
pub fn ensure_array(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<Array>> {
151151
Ok(match v {
152152
Value::Array(a) => a,
153153
_ => {

src/interpreter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3918,7 +3918,7 @@ impl Interpreter {
39183918
if value != Value::Undefined {
39193919
for (path, value_in_map) in value.as_object()? {
39203920
let mut full_path = package_components.clone();
3921-
full_path.append(&mut path.as_array()?.clone());
3921+
full_path.append(&mut path.as_array()?.to_vec());
39223922
self.check_rule_path(refr, &full_path, value_in_map, is_set)?;
39233923
self.update_rule_value(
39243924
span,

0 commit comments

Comments
 (0)