Files
regorus/src/languages/rego/compiler/error.rs
Copilot dae3052781 fix(interpreter,rvm): correct partial object rule iteration and classification (#718)
Partial object rules with dynamic keys (e.g. `violations[k] if { ... }`)
only produced a single entry instead of collecting all bindings. Two
independent bugs caused this:

1. Interpreter: the early-return optimization in eval_output_expr_in_loop
   checked whether the rule_ref was constant but never verified whether
   the key expression was also constant. A variable key like `k` was
   treated as constant output, causing the loop to exit after the first
   iteration. Fixed by gating early-return on key_expr constness.

2. RVM: compute_rule_type incorrectly classified `p[k] if { ... }` as
   PartialSet instead of PartialObject. OPA v1 semantics define this
   form as a partial object (key -> true). Fixed the classification and
   added compiler error guards for patterns the RVM codegen cannot yet
   handle (constant keys, nested bracket keys), ensuring graceful
   fallback to the interpreter.

The OPA test harness now skips RVM validation per-case when partial
object compiler errors are raised, rather than blanket-skipping entire
folders. This preserves RVM coverage for unrelated tests in the same
folders.

Closes #712

Co-authored-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-05-18 15:14:08 -05:00

135 lines
3.6 KiB
Rust

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(clippy::pattern_type_mismatch)]
use alloc::format;
use alloc::string::String;
use crate::lexer::Span;
use core::fmt;
#[derive(thiserror::Error, Debug)]
pub enum CompilerError {
#[error("Not a builtin function: {name}")]
NotBuiltinFunction { name: String },
#[error("Unknown builtin function: {name}")]
UnknownBuiltinFunction { name: String },
#[error("the `with` keyword is not supported by the compiler yet")]
WithKeywordUnsupported,
#[error("internal: missing context for yield")]
MissingYieldContext,
#[error(
"Direct access to 'data' root is not allowed. Use a specific path like 'data.package.rule'"
)]
DirectDataAccess,
#[error("Not a simple reference chain")]
NotSimpleReferenceChain,
#[error("Missing binding plan for {context}")]
MissingBindingPlan { context: String },
#[error("Unexpected binding plan variant for {context}: {found}")]
UnexpectedBindingPlan { context: String, found: String },
#[error("Invalid destructuring pattern in assignment")]
InvalidDestructuringPattern,
#[error("Unsupported expression type in chained reference")]
UnsupportedChainedExpression,
#[error("internal: no rule type found for '{rule_path}'")]
RuleTypeNotFound { rule_path: String },
#[error("unary - can only be used with numeric literals")]
InvalidUnaryMinus,
#[error("Unknown function: '{name}'")]
UnknownFunction { name: String },
#[error("Undefined variable: '{name}'")]
UndefinedVariable { name: String },
#[error("SomeIn should have been hoisted as a loop")]
SomeInNotHoisted,
#[error("Invalid function expression")]
InvalidFunctionExpression,
#[error("Invalid function expression with package")]
InvalidFunctionExpressionWithPackage,
#[error("partial object rules with constant keys are not yet supported by the RVM compiler")]
PartialObjectConstantKeyUnsupported,
#[error(
"partial object rules with nested bracket keys are not yet supported by the RVM compiler"
)]
PartialObjectNestedKeyUnsupported,
#[error("Compilation error: {message}")]
General { message: String },
}
impl From<anyhow::Error> for CompilerError {
fn from(err: anyhow::Error) -> Self {
CompilerError::General {
message: format!("{}", err),
}
}
}
#[derive(Debug)]
pub struct SpannedCompilerError {
pub error: CompilerError,
pub span: Option<Span>,
}
impl SpannedCompilerError {
pub fn new(error: CompilerError) -> Self {
Self { error, span: None }
}
pub fn with_span(mut self, span: &Span) -> Self {
if self.span.is_none() {
self.span = Some(span.clone());
}
self
}
}
impl fmt::Display for SpannedCompilerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(span) = &self.span {
let msg = format!("{}", self.error);
write!(f, "{}", span.message("error", &msg))
} else {
write!(f, "{}", self.error)
}
}
}
impl From<CompilerError> for SpannedCompilerError {
fn from(error: CompilerError) -> Self {
Self::new(error)
}
}
impl core::error::Error for SpannedCompilerError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.error)
}
}
impl CompilerError {
pub fn at(self, span: &Span) -> SpannedCompilerError {
SpannedCompilerError::from(self).with_span(span)
}
}
pub type Result<T> = ::core::result::Result<T, SpannedCompilerError>;