fix: Implement RVM set ops correctly

- treat set subtraction in RVM the same as the interpreter by supporting
  Value::Set operands in sub_values
- emit internal-only builtin names for set union/intersection and register
  handlers so compiled bytecode resolves without exposing new Rego builtins
- add regression coverage for literal set difference/intersection
  (x/y from failure.rego) in tests/rvm/rego/cases/sets.yaml

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2025-12-03 14:38:17 -06:00
parent 252ae0e312
commit a514e8da83
4 changed files with 58 additions and 2 deletions

View File

@@ -15,6 +15,8 @@ use anyhow::{bail, Result};
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("intersection", (intersection_of_set_of_sets, 1));
m.insert("union", (union_of_set_of_sets, 1));
m.insert("__builtin_sets.union", (binary_set_union, 2));
m.insert("__builtin_sets.intersection", (binary_set_intersection, 2));
}
pub fn intersection(expr1: &Expr, expr2: &Expr, v1: Value, v2: Value) -> Result<Value> {
@@ -35,6 +37,34 @@ pub fn difference(expr1: &Expr, expr2: &Expr, v1: Value, v2: Value) -> Result<Va
Ok(Value::from_set(s1.difference(&s2).cloned().collect()))
}
fn binary_set_union(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "__builtin_sets.union";
ensure_args_count(span, name, params, args, 2)?;
let left = ensure_set(name, &params[0], args[0].clone())?;
let right = ensure_set(name, &params[1], args[1].clone())?;
Ok(Value::from_set(left.union(&right).cloned().collect()))
}
fn binary_set_intersection(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "__builtin_sets.intersection";
ensure_args_count(span, name, params, args, 2)?;
let left = ensure_set(name, &params[0], args[0].clone())?;
let right = ensure_set(name, &params[1], args[1].clone())?;
Ok(Value::from_set(
left.intersection(&right).cloned().collect(),
))
}
fn intersection_of_set_of_sets(
span: &Span,
params: &[Ref<Expr>],

View File

@@ -142,7 +142,7 @@ impl<'a> Compiler<'a> {
match op {
BinOp::Union => {
let builtin_index = self.get_builtin_index("sets.union")?;
let builtin_index = self.get_builtin_index("__builtin_sets.union")?;
let params = BuiltinCallParams {
dest,
builtin_index,
@@ -156,7 +156,7 @@ impl<'a> Compiler<'a> {
self.emit_instruction(Instruction::BuiltinCall { params_index }, span);
}
BinOp::Intersection => {
let builtin_index = self.get_builtin_index("sets.intersection")?;
let builtin_index = self.get_builtin_index("__builtin_sets.intersection")?;
let params = BuiltinCallParams {
dest,
builtin_index,

View File

@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::collections::BTreeSet;
use crate::number::Number;
use crate::value::Value;
@@ -23,6 +25,10 @@ impl RegoVM {
pub(super) fn sub_values(&self, a: &Value, b: &Value) -> Result<Value> {
match (a, b) {
(Value::Number(x), Value::Number(y)) => Ok(Value::from(x.sub(y)?)),
(Value::Set(left), Value::Set(right)) => {
let diff: BTreeSet<Value> = left.difference(right).cloned().collect();
Ok(Value::from_set(diff))
}
_ => Err(VmError::InvalidSubtraction {
left: a.clone(),
right: b.clone(),

View File

@@ -67,3 +67,23 @@ cases:
- set!: [1, 2]
- set!: [3, 4]
- set!: ["a", "b"]
- note: set_difference_literals
data: {}
modules:
- |
package test
x := {2, 3} - {4, 2}
query: data.test.x
want_result:
set!: [3]
- note: set_intersection_literals
data: {}
modules:
- |
package test
y := {2, 3} & {4, 2}
query: data.test.y
want_result:
set!: [2]