Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion bindings/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,8 @@ mod tests {
assert_eq!(
r["files"][0]["covered"]
.as_array()
.map_err(crate::error_to_jsvalue)?,
.map_err(crate::error_to_jsvalue)?
.as_slice(),
&vec![regorus::Value::from(3)]
);

Expand Down
33 changes: 33 additions & 0 deletions docs/value/array.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Array

Opaque container for `Value::Array`'s ordered element storage, enabling
alternative backends without call-site changes. It follows the same
abstraction pattern as [`Object`](object.md) and [`Set`](set.md).

## Design

`Array` wraps a `Vec<Value>` today but exposes only a curated method surface
(`get`, `get_mut`, `first`, `last`, `contains`, `iter`, `iter_mut`, `push`,
`append`, `extend`, `extend_from_slice`, `retain`, `clear`, `reverse`, `sort`,
`cursor`, and serde). The inner vector is private, and `Array` does not
implement `Deref`, so callers cannot depend on the backing representation.

Indexing uses `Index<usize>` and returns `Value::Undefined` for an out-of-range
index, matching `Value` indexing semantics. Use `get` when distinguishing a
missing element from an element whose value is explicitly `Undefined`.

Iteration follows sequence order. The opaque cursor supports incremental
traversal needed by RVM iteration state without exposing iterator internals.
`Ord` is implemented against the sequence iterator so alternative backends can
preserve the current array comparison behavior.

## Scenarios enabled

- **Inline-small storage** — store short arrays inline and spill to the heap
only for larger values.
- **Lazy/streaming storage** — materialize elements from JSON, CBOR, or a host
provider on demand.
- **Arena allocation** — use bump allocation for evaluation-time temporaries
and release them together at query end.
- **FFI-backed storage** — access host-language lists or arrays without
copying at every binding boundary.
2 changes: 1 addition & 1 deletion src/builtins/arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,6 @@ fn slice(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
return Ok(Value::new_array());
}

let slice = &array[start..stop];
let slice = array.as_slice().get(start..stop).unwrap_or_default();
Ok(Value::from(slice.to_vec()))
}
10 changes: 7 additions & 3 deletions src/builtins/azure_policy/template_functions_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn fn_intersection(
match *first {
Value::Array(ref first) => {
// Intersection of arrays: keep elements from first that appear in all others.
let mut result: Vec<Value> = first.as_ref().clone();
let mut result: Vec<Value> = first.as_slice().to_vec();
for arg in rest {
let Value::Array(ref other) = *arg else {
return Ok(Value::Undefined);
Expand Down Expand Up @@ -149,7 +149,9 @@ fn fn_take(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -
match *original {
Value::Array(ref arr) => {
let n = count.min(arr.len());
Ok(Value::from(arr.get(..n).unwrap_or_default().to_vec()))
Ok(Value::from(
arr.as_slice().get(..n).unwrap_or_default().to_vec(),
))
}
Value::String(ref s) => {
let taken: alloc::string::String = s.chars().take(count).collect();
Expand All @@ -172,7 +174,9 @@ fn fn_skip(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -
match *original {
Value::Array(ref arr) => {
let n = count.min(arr.len());
Ok(Value::from(arr.get(n..).unwrap_or_default().to_vec()))
Ok(Value::from(
arr.as_slice().get(n..).unwrap_or_default().to_vec(),
))
}
Value::String(ref s) => {
let skipped: alloc::string::String = s.chars().skip(count).collect();
Expand Down
2 changes: 1 addition & 1 deletion src/builtins/azure_policy/template_functions_misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fn fn_items(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool)
entry.insert(Value::from("value"), v.clone());
result.push(Value::Object(Rc::new(entry)));
}
Ok(Value::Array(Rc::new(result)))
Ok(Value::from_array(result))
}

// ── indexFromEnd ──────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion src/builtins/json_patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl EditNode {
array.push(value.render()?);
enforce_limit()?;
}
Value::Array(crate::Rc::new(array))
Value::Array(crate::Rc::new(crate::value::Array::from(array)))
}
Self::Set(members) => {
let mut set = BTreeSet::new();
Expand Down
2 changes: 1 addition & 1 deletion src/builtins/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ fn _cidr_expand(cidr: Arc<str>) -> Result<Value> {
enforce_limit()?;
}

Ok(Value::Array(Arc::from(hosts)))
Ok(Value::from_array(hosts))
}

#[cfg(test)]
Expand Down
7 changes: 5 additions & 2 deletions src/builtins/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,10 @@ fn is_subset(sup: &Value, sub: &Value) -> bool {
})
}
(Value::Set(sup), Value::Set(sub)) => sub.is_subset(sup),
(Value::Array(sup), Value::Array(sub)) => sup.windows(sub.len()).any(|w| w == &sub[..]),
(Value::Array(sup), Value::Array(sub)) => sup
.as_slice()
.windows(sub.len())
.any(|w| w == sub.as_slice()),
(Value::Array(sup), Value::Set(_)) => {
let sup = Value::from_set(sup.iter().cloned().collect());
is_subset(&sup, sub)
Expand Down Expand Up @@ -504,7 +507,7 @@ fn json_patch(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)

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

let patched = super::json_patch::apply(&args[0], ops);
let patched = super::json_patch::apply(&args[0], ops.as_slice());
match patched {
Ok(patched) => Ok(patched),
// Resource-limit errors must propagate rather than look like an
Expand Down
4 changes: 2 additions & 2 deletions src/builtins/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use crate::ast::{Expr, Ref};
use crate::lexer::Span;
use crate::number::Number;
use crate::value::{Object, Set};
use crate::value::{Array, Object, Set};
use crate::Rc;
use crate::Value;
use crate::*;
Expand Down Expand Up @@ -147,7 +147,7 @@ pub fn ensure_string_collection<'a>(fcn: &str, arg: &Expr, v: &'a Value) -> Resu
Ok(collection)
}

pub fn ensure_array(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<Vec<Value>>> {
pub fn ensure_array(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<Array>> {
Ok(match v {
Value::Array(a) => a,
_ => {
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3918,7 +3918,7 @@ impl Interpreter {
if value != Value::Undefined {
for (path, value_in_map) in value.as_object()? {
let mut full_path = package_components.clone();
full_path.append(&mut path.as_array()?.clone());
full_path.append(&mut path.as_array()?.to_vec());
self.check_rule_path(refr, &full_path, value_in_map, is_set)?;
self.update_rule_value(
span,
Expand Down
2 changes: 1 addition & 1 deletion src/languages/azure_policy/aliases/obj_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub fn make_value(map: ObjMap) -> Value {

/// Convert a `Vec<Value>` into a `Value::Array`.
pub fn make_array(items: Vec<Value>) -> Value {
Value::Array(Rc::new(items))
Value::from_array(items)
}

/// Extract a `&str` from a `Value::String`.
Expand Down
2 changes: 1 addition & 1 deletion src/languages/azure_rbac/builtins/lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use super::evaluator::RbacBuiltinError;
pub(super) fn list_contains(left: &Value, right: &Value) -> Result<bool, RbacBuiltinError> {
match *left {
// Lists and sets share containment semantics.
Value::Array(ref list) => Ok(list_contains_values(list, right)),
Value::Array(ref list) => Ok(list_contains_values(list.as_slice(), right)),
Value::Set(ref set) => Ok(set_contains_values(set, right)),
_ => Ok(false),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub(in crate::languages::rego::compiler) fn try_eval_const(expr: &Expr) -> Optio
.iter()
.map(|i| try_eval_const(i.as_ref()))
.collect::<Option<Vec<_>>>()
.map(|v| Value::Array(Rc::new(v))),
.map(Value::from_array),
Expr::Set { items, .. } => items
.iter()
.map(|i| try_eval_const(i.as_ref()))
Expand All @@ -59,7 +59,7 @@ impl<'a> Compiler<'a> {
let all_const: Option<Vec<_>> = items.iter().map(|i| try_eval_const(i.as_ref())).collect();
if let Some(values) = all_const {
let dest = self.alloc_register();
let literal_idx = self.add_literal(Value::Array(Rc::new(values)));
let literal_idx = self.add_literal(Value::from_array(values));
self.emit_instruction(Instruction::Load { dest, literal_idx }, span);
return Ok(dest);
}
Expand Down
2 changes: 1 addition & 1 deletion src/rvm/program/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ impl MetadataValue {
MetadataValue::Integer(n) => Value::from(n),
MetadataValue::List(ref list) => {
let values: Vec<Value> = list.iter().map(MetadataValue::to_value).collect();
Value::Array(Rc::new(values))
Value::from_array(values)
}
MetadataValue::Map(ref map) => {
let mut obj = Object::new();
Expand Down
9 changes: 4 additions & 5 deletions src/rvm/vm/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
// Licensed under the MIT License.

use crate::rvm::instructions::{ComprehensionMode, LoopMode};
use crate::value::Value;
use crate::value::{Object, ObjectCursor, Set, SetCursor};
use crate::value::{Array, Object, ObjectCursor, Set, SetCursor};
use crate::Rc;
use alloc::vec::Vec;

/// Loop execution context for managing iteration state
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -36,7 +34,7 @@ pub struct LoopContext {
#[derive(Debug, Clone)]
pub enum IterationState {
Array {
items: Rc<Vec<Value>>,
items: Rc<Array>,
index: usize,
},
Object {
Expand Down Expand Up @@ -134,7 +132,8 @@ pub(super) struct ComprehensionContext {
)]
mod tests {
use super::*;
use crate::value::Object;
use crate::value::{Object, Value};
use alloc::vec::Vec;

/// IterationState::Object holds an `Rc<Object>` plus an opaque cursor.
/// Mutating an aliased Rc via `Rc::make_mut` allocates a new collection
Expand Down
4 changes: 2 additions & 2 deletions src/rvm/vm/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ impl RegoVM {
}
}
ArrayNew { dest } => {
let empty_array = Value::Array(crate::Rc::new(Vec::new()));
let empty_array = Value::new_array();
self.set_register(dest, empty_array)?;
Ok(InstructionOutcome::Continue)
}
Expand Down Expand Up @@ -656,7 +656,7 @@ impl RegoVM {
.map(|&reg| self.get_register(reg).cloned())
.collect::<Result<Vec<_>>>()?;

let array_value = Value::Array(crate::Rc::new(elements));
let array_value = Value::from_array(elements);
self.set_register(params.dest, array_value)?;
}
Ok(InstructionOutcome::Continue)
Expand Down
5 changes: 1 addition & 4 deletions src/schema/tests/suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -976,10 +976,7 @@ fn test_deserialize_array_items_with_fields() {
assert_eq!(description.as_deref(), Some("outer array"));
assert_eq!(min_items, &Some(1));
assert_eq!(max_items, &Some(2));
assert_eq!(
default,
&Some(Value::Array(Rc::new(vec![Value::from("bar")])))
);
assert_eq!(default, &Some(Value::from_array(vec![Value::from("bar")])));
match items.as_type() {
Type::String {
description,
Expand Down
Loading
Loading