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
5 changes: 5 additions & 0 deletions cedar-policy-core/src/ast/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ impl ExtensionFunction {
name.clone(),
0,
args.len(),
None, // evaluator will add the source location later
))
}
}),
Expand All @@ -184,6 +185,7 @@ impl ExtensionFunction {
name.clone(),
1,
args.len(),
None, // evaluator will add the source location later
)),
}),
None,
Expand All @@ -208,6 +210,7 @@ impl ExtensionFunction {
name.clone(),
1,
args.len(),
None, // evaluator will add the source location later
)),
}),
Some(return_type),
Expand All @@ -234,6 +237,7 @@ impl ExtensionFunction {
name.clone(),
2,
args.len(),
None, // evaluator will add the source location later
)),
}),
Some(return_type),
Expand Down Expand Up @@ -263,6 +267,7 @@ impl ExtensionFunction {
name.clone(),
3,
args.len(),
None, // evaluator will add the source location later
)),
}),
Some(return_type),
Expand Down
22 changes: 21 additions & 1 deletion cedar-policy-core/src/ast/restricted_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ impl<'a> Hash for RestrictedExprShapeOnly<'a> {

/// Error when constructing a restricted expression from unrestricted

#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum RestrictedExprError {
/// An expression was expected to be a "restricted" expression, but contained
/// a feature that is not allowed in restricted expressions. The `feature`
Expand All @@ -616,6 +616,26 @@ pub enum RestrictedExprError {
},
}

// custom impl of `Diagnostic`: take location info from the embedded subexpression
impl Diagnostic for RestrictedExprError {
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
match self {
Self::InvalidRestrictedExpression { expr, .. } => expr.source_loc().map(|loc| {
Box::new(std::iter::once(miette::LabeledSpan::underline(loc.span)))
as Box<dyn Iterator<Item = _>>
}),
}
}

fn source_code(&self) -> Option<&dyn miette::SourceCode> {
match self {
Self::InvalidRestrictedExpression { expr, .. } => expr
.source_loc()
.map(|loc| &loc.src as &dyn miette::SourceCode),
}
}
}

/// Errors possible from `RestrictedExpr::from_str()`
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
pub enum RestrictedExprParseError {
Expand Down
1,118 changes: 745 additions & 373 deletions cedar-policy-core/src/evaluator.rs

Large diffs are not rendered by default.

127 changes: 96 additions & 31 deletions cedar-policy-core/src/evaluator/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
*/

use crate::ast::*;
use crate::parser::Loc;
use itertools::Itertools;
use miette::Diagnostic;
use miette::{Diagnostic, LabeledSpan};
use nonempty::{nonempty, NonEmpty};
use smol_str::SmolStr;
use std::sync::Arc;
Expand All @@ -30,10 +31,13 @@ pub struct EvaluationError {
error_kind: EvaluationErrorKind,
/// Optional advice on how to fix the error
advice: Option<String>,
/// Source location of the error. (This overrides other sources if present,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems error-prone to me. Maybe create an issue to fix it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to create an issue, curious what you find to be error-prone about this?

Are you concerned that the source location in this field might disagree with the source location embedded in error_kind? If it makes you feel better, the vast majority of the EvaluationErrorKinds do not have a source loc field -- the only cases that do have a source loc are InvalidRestrictedExpression and NonValue, and those just because they store an Expr as part of the error. We could make an issue to remove these somehow, so that error_kind never contains a source loc; is that what you're suggesting?

Going the other way -- having all the source locs in error_kind so that we never need this field -- is backwards; we established recently that we prefer the convention to use the *Kind pattern to have a single place the source loc field is defined, rather than adding a source loc field in N places, once for each enum variant. The struct here conforms to that practice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's just a general concern about "code smell," not anything concrete. Even if we always remember to keep them in sync, I'd worry about that complexity hiding other bugs. E.g., I think the bug we had where it was possible to add a link and a template with colliding names wouldn't have happened if we didn't have duplicate information in our lists of policies+templates and links+templates. Even though it wasn't caused by forgetting to keep data in sync, the added complexity (e.g., rolling back changes to one representation if modifying the other failed) distracts code reviewers from the obvious "you forgot to check that no link with this ID exists."

/// but if this is `None`, we'll check for location info in the
/// `.error_kind`.)
source_loc: Option<Loc>,
}

// custom impl of `Diagnostic`: non-trivial implementation of `help()`,
// everything else forwarded to `.error_kind`
// custom impl of `Diagnostic`
impl Diagnostic for EvaluationError {
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
match (self.error_kind.help(), self.advice.as_ref()) {
Expand All @@ -44,6 +48,23 @@ impl Diagnostic for EvaluationError {
}
}

fn source_code(&self) -> Option<&dyn miette::SourceCode> {
self.source_loc
.as_ref()
.map(|loc| &loc.src as &dyn miette::SourceCode)
.or_else(|| self.error_kind.source_code())
}

fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
self.source_loc
.as_ref()
.map(|loc| {
Box::new(std::iter::once(LabeledSpan::underline(loc.span)))
as Box<dyn Iterator<Item = _>>
})
.or_else(|| self.error_kind.labels())
}

fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.error_kind.code()
}
Expand All @@ -56,14 +77,6 @@ impl Diagnostic for EvaluationError {
self.error_kind.url()
}

fn source_code(&self) -> Option<&dyn miette::SourceCode> {
self.error_kind.source_code()
}

fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
self.error_kind.labels()
}

fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
self.error_kind.diagnostic_source()
}
Expand All @@ -79,137 +92,188 @@ impl EvaluationError {
&self.error_kind
}

/// Extract the source location of the error, if one is attached
pub fn source_loc(&self) -> Option<&Loc> {
self.source_loc.as_ref()
}

/// Extract the advice attached to the error, if any
pub fn advice(&self) -> Option<&str> {
self.advice.as_deref()
}

/// Set the advice field of an error
pub fn set_advice(&mut self, advice: String) {
self.advice = Some(advice);
}

/// Return the `EvaluationError`, but with the new `source_loc` (or `None`).
pub(crate) fn with_maybe_source_loc(self, source_loc: Option<Loc>) -> Self {
Self { source_loc, ..self }
}

/// Construct a [`EntityDoesNotExist`] error
pub(crate) fn entity_does_not_exist(euid: Arc<EntityUID>) -> Self {
pub(crate) fn entity_does_not_exist(euid: Arc<EntityUID>, source_loc: Option<Loc>) -> Self {
Self {
error_kind: EvaluationErrorKind::EntityDoesNotExist(euid),
advice: None,
source_loc,
}
}

/// Construct a [`EntityAttrDoesNotExist`] error
pub(crate) fn entity_attr_does_not_exist(entity: Arc<EntityUID>, attr: SmolStr) -> Self {
pub(crate) fn entity_attr_does_not_exist(
entity: Arc<EntityUID>,
attr: SmolStr,
source_loc: Option<Loc>,
) -> Self {
Self {
error_kind: EvaluationErrorKind::EntityAttrDoesNotExist { entity, attr },
advice: None,
source_loc,
}
}

/// Construct a [`UnspecifiedEntityAccess`] error
pub(crate) fn unspecified_entity_access(attr: SmolStr) -> Self {
pub(crate) fn unspecified_entity_access(attr: SmolStr, source_loc: Option<Loc>) -> Self {
Self {
error_kind: EvaluationErrorKind::UnspecifiedEntityAccess(attr),
advice: None,
source_loc,
}
}

/// Construct a [`RecordAttrDoesNotExist`] error
pub(crate) fn record_attr_does_not_exist(attr: SmolStr, alternatives: Vec<SmolStr>) -> Self {
pub(crate) fn record_attr_does_not_exist(
attr: SmolStr,
alternatives: Vec<SmolStr>,
source_loc: Option<Loc>,
) -> Self {
Self {
error_kind: EvaluationErrorKind::RecordAttrDoesNotExist(attr, alternatives),
advice: None,
source_loc,
}
}

/// Construct a [`TypeError`] error
pub(crate) fn type_error(expected: NonEmpty<Type>, actual: Type) -> Self {
pub(crate) fn type_error(expected: NonEmpty<Type>, actual: &Value) -> Self {
Self {
error_kind: EvaluationErrorKind::TypeError { expected, actual },
error_kind: EvaluationErrorKind::TypeError {
expected,
actual: actual.type_of(),
},
advice: None,
source_loc: actual.source_loc().cloned(),
}
}

pub(crate) fn type_error_single(expected: Type, actual: Type) -> Self {
pub(crate) fn type_error_single(expected: Type, actual: &Value) -> Self {
Self::type_error(nonempty![expected], actual)
}

/// Construct a [`TypeError`] error with the advice field set
pub(crate) fn type_error_with_advice(
expected: NonEmpty<Type>,
actual: Type,
actual: &Value,
advice: String,
) -> Self {
Self {
error_kind: EvaluationErrorKind::TypeError { expected, actual },
error_kind: EvaluationErrorKind::TypeError {
expected,
actual: actual.type_of(),
},
advice: Some(advice),
source_loc: actual.source_loc().cloned(),
}
}

pub(crate) fn type_error_with_advice_single(
expected: Type,
actual: Type,
actual: &Value,
advice: String,
) -> Self {
Self::type_error_with_advice(nonempty![expected], actual, advice)
}

/// Construct a [`WrongNumArguments`] error
pub(crate) fn wrong_num_arguments(function_name: Name, expected: usize, actual: usize) -> Self {
pub(crate) fn wrong_num_arguments(
function_name: Name,
expected: usize,
actual: usize,
source_loc: Option<Loc>,
) -> Self {
Self {
error_kind: EvaluationErrorKind::WrongNumArguments {
function_name,
expected,
actual,
},
advice: None,
source_loc,
}
}

/// Construct a [`UnlinkedSlot`] error
pub(crate) fn unlinked_slot(id: SlotId) -> Self {
pub(crate) fn unlinked_slot(id: SlotId, source_loc: Option<Loc>) -> Self {
Self {
error_kind: EvaluationErrorKind::UnlinkedSlot(id),
advice: None,
source_loc,
}
}

/// Construct a [`FailedExtensionFunctionApplication`] error
pub(crate) fn failed_extension_function_application(extension_name: Name, msg: String) -> Self {
pub(crate) fn failed_extension_function_application(
extension_name: Name,
msg: String,
source_loc: Option<Loc>,
) -> Self {
Self {
error_kind: EvaluationErrorKind::FailedExtensionFunctionApplication {
extension_name,
msg,
},
advice: None,
source_loc,
}
}

/// Construct a [`NonValue`] error
pub(crate) fn non_value(e: Expr) -> Self {
let source_loc = e.source_loc().cloned();
Self {
error_kind: EvaluationErrorKind::NonValue(e),
advice: Some("consider using the partial evaluation APIs".into()),
source_loc,
}
}

/// Construct a [`RecursionLimit`] error
pub(crate) fn recursion_limit() -> Self {
pub(crate) fn recursion_limit(source_loc: Option<Loc>) -> Self {
Self {
error_kind: EvaluationErrorKind::RecursionLimit,
advice: None,
source_loc,
}
}
}

impl From<crate::extensions::ExtensionFunctionLookupError> for EvaluationError {
fn from(err: crate::extensions::ExtensionFunctionLookupError) -> Self {
pub(crate) fn extension_function_lookup(
err: crate::extensions::ExtensionFunctionLookupError,
source_loc: Option<Loc>,
) -> Self {
Self {
error_kind: err.into(),
advice: None,
source_loc,
}
}
}

impl From<IntegerOverflowError> for EvaluationError {
fn from(err: IntegerOverflowError) -> Self {
pub(crate) fn integer_overflow(err: IntegerOverflowError, source_loc: Option<Loc>) -> Self {
Self {
error_kind: err.into(),
advice: None,
source_loc,
}
}
}
Expand All @@ -219,6 +283,7 @@ impl From<RestrictedExprError> for EvaluationError {
Self {
error_kind: err.into(),
advice: None,
source_loc: None, // defer to the source information embedded in the `RestrictedExprError` and thus stored in `error_kind`
}
}
}
Expand Down
Loading