diff --git a/src/interpreter.rs b/src/interpreter.rs index 5e9ea84..dc990dd 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1757,13 +1757,14 @@ impl Interpreter { match expr.as_ref() { Expr::Var { span, .. } => { - // A variable that is not currently bound in local scope behaves like + // A variable that is not currently bound in any active local scope behaves like // a stable global/package reference for this evaluation. - let scope = self + let is_bound = self .scopes - .last() - .ok_or_else(|| anyhow!("internal error: no current scope"))?; - Ok(!scope.contains_key(&span.source_str())) + .iter() + .rev() + .any(|scope| scope.contains_key(&span.source_str())); + Ok(!is_bound) } Expr::RefDot { refr, .. } => self.is_constant_key_expr(refr), Expr::RefBrack { refr, index, .. } => { diff --git a/src/languages/rego/compiler/program.rs b/src/languages/rego/compiler/program.rs index 2f4b51f..43c4cb6 100644 --- a/src/languages/rego/compiler/program.rs +++ b/src/languages/rego/compiler/program.rs @@ -181,6 +181,10 @@ impl<'a> Compiler<'a> { } fn evaluate_default_rule(&mut self, rule_path: &str) -> Option { + if !self.may_have_default_rule(rule_path) { + return None; + } + let mut interpreter = Interpreter::new_from_compiled_policy(self.policy.inner.clone()); match interpreter.eval_default_rule_for_compiler(rule_path) { @@ -196,6 +200,33 @@ impl<'a> Compiler<'a> { None } + fn may_have_default_rule(&self, rule_path: &str) -> bool { + if self.policy.inner.default_rules.contains_key(rule_path) { + return true; + } + + let Some((parent_path, index)) = rule_path.rsplit_once('.') else { + return false; + }; + + self.policy + .inner + .default_rules + .get(parent_path) + .is_some_and(|rules| { + rules + .iter() + .any(|(_, rule_index)| match rule_index.as_deref() { + Some(rule_index) if rule_index == index => true, + Some(rule_index) => rule_index + .strip_prefix('"') + .and_then(|rule_index| rule_index.strip_suffix('"')) + .is_some_and(|rule_index| rule_index == index), + None => false, + }) + }) + } + fn extract_destructuring_blocks(&self, rule_index: u16) -> Vec> { self.rule_definition_destructuring_patterns[rule_index as usize].clone() } diff --git a/tests/interpreter/cases/default/basic.yaml b/tests/interpreter/cases/default/basic.yaml index 325cc33..506d9ce 100644 --- a/tests/interpreter/cases/default/basic.yaml +++ b/tests/interpreter/cases/default/basic.yaml @@ -349,7 +349,7 @@ cases: package test import rego.v1 default config["timeout"] := 30 - config["timeout"] := input.val if { + config["timeout"] := val if { val := input.val } query: data.test.config.timeout @@ -364,7 +364,7 @@ cases: package test import rego.v1 default config["timeout"] := 30 - config["timeout"] := input.val if { + config["timeout"] := val if { val := input.val } query: data.test.config.timeout diff --git a/tests/interpreter/cases/rule/partial_object_iteration.yaml b/tests/interpreter/cases/rule/partial_object_iteration.yaml index ce2e153..d3205a9 100644 --- a/tests/interpreter/cases/rule/partial_object_iteration.yaml +++ b/tests/interpreter/cases/rule/partial_object_iteration.yaml @@ -201,6 +201,27 @@ cases: BAZ: true FOO: true + - note: partial_object_key_bound_in_outer_scope_v1 + data: {} + input: + outer: + FOO: [1, 2] + BAR: [3] + BAZ: [] + modules: + - | + package test + import rego.v1 + + violations[k] if { + some k, arr in input.outer + some _ in arr + } + query: data.test.violations + want_result: + BAR: true + FOO: true + - note: complete_rule_same_value_definitions_still_work_v1 data: {} input: diff --git a/tests/rvm/rego/cases/default_rules.yaml b/tests/rvm/rego/cases/default_rules.yaml index 4857f0d..4933388 100644 --- a/tests/rvm/rego/cases/default_rules.yaml +++ b/tests/rvm/rego/cases/default_rules.yaml @@ -55,7 +55,7 @@ cases: package test import rego.v1 default config["timeout"] := 30 - config["timeout"] := input.val if { + config["timeout"] := val if { val := input.val } query: data.test.config.timeout @@ -70,7 +70,7 @@ cases: package test import rego.v1 default config["timeout"] := 30 - config["timeout"] := input.val if { + config["timeout"] := val if { val := input.val } query: data.test.config.timeout diff --git a/tests/rvm/rego/cases/partial_object_rules.yaml b/tests/rvm/rego/cases/partial_object_rules.yaml index 0a1f7d2..eb5ad37 100644 --- a/tests/rvm/rego/cases/partial_object_rules.yaml +++ b/tests/rvm/rego/cases/partial_object_rules.yaml @@ -186,7 +186,7 @@ cases: seen contains k if { some k, _ in input.x } - + main := { "seen": seen, "violations": violations, @@ -200,6 +200,27 @@ cases: BAZ: true FOO: true + - note: partial_object_key_bound_in_outer_scope_v1 + data: {} + input: + outer: + FOO: [1, 2] + BAR: [3] + BAZ: [] + modules: + - | + package test + import rego.v1 + + violations[k] if { + some k, arr in input.outer + some _ in arr + } + query: data.test.violations + want_result: + BAR: true + FOO: true + - note: complete_rule_same_value_definitions_still_work_v1 data: {} input: