fix: preserve all bindings for partial object iteration

Agent-Logs-Url: https://github.com/microsoft/regorus/sessions/34a4e1b3-d364-46c4-9998-b00780f5d339

Co-authored-by: anakrish <35780660+anakrish@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-05-06 19:34:41 +00:00
committed by GitHub
parent 8617c79da5
commit 117671d959
7 changed files with 424 additions and 7 deletions

View File

@@ -1797,7 +1797,10 @@ impl Interpreter {
comps.pop();
output
} else {
// Rule's constness is determined only by its ref.
// Implicit-true partial object rules can vary with each successful key binding.
if key_expr.is_some() && !is_old_style_set {
is_const_rule = false;
}
Value::Bool(true)
};

View File

@@ -55,11 +55,8 @@ impl<'a> Compiler<'a> {
if let Rule::Spec { head, .. } = def.as_ref() {
match head {
RuleHead::Set { .. } => RuleType::PartialSet,
RuleHead::Compr { refr, assign, .. } => match refr.as_ref() {
crate::ast::Expr::RefBrack { .. } if assign.is_some() => {
RuleType::PartialObject
}
crate::ast::Expr::RefBrack { .. } => RuleType::PartialSet,
RuleHead::Compr { refr, .. } => match refr.as_ref() {
crate::ast::Expr::RefBrack { .. } => RuleType::PartialObject,
_ => RuleType::Complete,
},
_ => RuleType::Complete,

View File

@@ -454,7 +454,19 @@ impl RegoVM {
let mut obj_value = self.take_register(obj)?;
if let Ok(obj_mut) = obj_value.as_object_mut() {
obj_mut.insert(key_value, value_value);
match obj_mut.get(&key_value) {
Some(existing_value) if existing_value != &value_value => {
self.set_register(obj, obj_value)?;
return Err(VmError::RuleMultipleOutputs { pc: self.pc });
}
Some(_) => {
self.set_register(obj, obj_value)?;
return Ok(InstructionOutcome::Continue);
}
None => {
obj_mut.insert(key_value, value_value);
}
}
self.set_register(obj, obj_value)?;
} else {
let offending = obj_value.clone();

View File

@@ -209,6 +209,9 @@ pub enum VmError {
#[error("Rule-data conflict: {message} (pc={pc})")]
RuleDataConflict { message: String, pc: usize },
#[error("rules must not produce multiple outputs (pc={pc})")]
RuleMultipleOutputs { pc: usize },
#[error("Arithmetic error: {message} (pc={pc})")]
ArithmeticError { message: String, pc: usize },

View File

@@ -26,6 +26,7 @@ impl RegoVM {
| VmError::MemoryLimitExceeded { .. }
| VmError::RegexSizeLimitExceeded { .. }
| VmError::InstructionLimitExceeded { .. }
| VmError::RuleMultipleOutputs { .. }
)
}

View File

@@ -0,0 +1,201 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: partial_object_iteration_some_in_object_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
}
query: data.test
want_result:
violations:
BAR: true
BAZ: true
FOO: true
- note: partial_object_iteration_some_in_array_v1
data: {}
input:
arr: ["FOO", "BAR", "BAZ"]
modules:
- |
package test
import rego.v1
violations[v] if {
some _, v in input.arr
}
query: data.test
want_result:
violations:
BAR: true
BAZ: true
FOO: true
- note: partial_object_iteration_with_filter_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
k != "BAR"
}
query: data.test
want_result:
violations:
BAZ: true
FOO: true
- note: partial_object_iteration_input_lookup_future_keywords
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import future.keywords.if
violations[k] if {
input.x[k]
}
query: data.test
want_result:
violations:
BAR: true
BAZ: true
FOO: true
- note: partial_object_multiple_bodies_collect_all_keys_v1
data: {}
input:
primary:
FOO: 1
BAR: 2
secondary:
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.primary
}
violations[k] if {
some k, _ in input.secondary
}
query: data.test
want_result:
violations:
BAR: true
BAZ: true
FOO: true
- note: partial_object_duplicate_keys_same_value_are_ok_v1
data: {}
input:
arr: ["FOO", "FOO", "BAR"]
modules:
- |
package test
import rego.v1
violations[v] if {
some _, v in input.arr
}
query: data.test
want_result:
violations:
BAR: true
FOO: true
- note: partial_object_duplicate_keys_different_values_error_v1
data: {}
input:
entries:
- k: "FOO"
v: 1
- k: "FOO"
v: 2
modules:
- |
package test
import rego.v1
violations[k] := v if {
some entry in input.entries
k := entry.k
v := entry.v
}
query: data.test.violations
error: "rules must not produce multiple outputs"
- note: partial_object_and_partial_set_iteration_coexist_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
}
seen contains k if {
some k, _ in input.x
}
query: data.test
want_result:
seen:
set!: ["BAR", "BAZ", "FOO"]
violations:
BAR: true
BAZ: true
FOO: true
- note: complete_rule_same_value_definitions_still_work_v1
data: {}
input:
role: "superuser"
modules:
- |
package test
import rego.v1
allowed if {
input.role == "admin"
}
allowed if {
input.role == "superuser"
}
query: data.test.allowed
want_result: true

View File

@@ -0,0 +1,200 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: partial_object_iteration_some_in_object_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
}
query: data.test.violations
want_result:
BAR: true
BAZ: true
FOO: true
- note: partial_object_iteration_some_in_array_v1
data: {}
input:
arr: ["FOO", "BAR", "BAZ"]
modules:
- |
package test
import rego.v1
violations[v] if {
some _, v in input.arr
}
query: data.test.violations
want_result:
BAR: true
BAZ: true
FOO: true
- note: partial_object_iteration_with_filter_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
k != "BAR"
}
query: data.test.violations
want_result:
BAZ: true
FOO: true
- note: partial_object_iteration_input_lookup_future_keywords
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import future.keywords.if
violations[k] if {
input.x[k]
}
query: data.test.violations
want_result:
BAR: true
BAZ: true
FOO: true
- note: partial_object_multiple_bodies_collect_all_keys_v1
data: {}
input:
primary:
FOO: 1
BAR: 2
secondary:
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.primary
}
violations[k] if {
some k, _ in input.secondary
}
query: data.test.violations
want_result:
BAR: true
BAZ: true
FOO: true
- note: partial_object_duplicate_keys_same_value_are_ok_v1
data: {}
input:
arr: ["FOO", "FOO", "BAR"]
modules:
- |
package test
import rego.v1
violations[v] if {
some _, v in input.arr
}
query: data.test.violations
want_result:
BAR: true
FOO: true
- note: partial_object_duplicate_keys_different_values_error_v1
data: {}
input:
entries:
- k: "FOO"
v: 1
- k: "FOO"
v: 2
modules:
- |
package test
import rego.v1
violations[k] := v if {
some entry in input.entries
k := entry.k
v := entry.v
}
query: data.test.violations
want_error: "multiple outputs"
- note: partial_object_and_partial_set_iteration_coexist_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
}
seen contains k if {
some k, _ in input.x
}
main := {
"seen": seen,
"violations": violations,
}
query: data.test.main
want_result:
seen:
set!: ["BAR", "BAZ", "FOO"]
violations:
BAR: true
BAZ: true
FOO: true
- note: complete_rule_same_value_definitions_still_work_v1
data: {}
input:
role: "superuser"
modules:
- |
package test
import rego.v1
allowed if {
input.role == "admin"
}
allowed if {
input.role == "superuser"
}
query: data.test.allowed
want_result: true