Skip to content

Commit dddea63

Browse files
authored
add source locations to evaluation errors (#582)
1 parent 06fe57b commit dddea63

File tree

8 files changed

+946
-462
lines changed

8 files changed

+946
-462
lines changed

cedar-policy-core/src/ast/extension.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ impl ExtensionFunction {
160160
name.clone(),
161161
0,
162162
args.len(),
163+
None, // evaluator will add the source location later
163164
))
164165
}
165166
}),
@@ -184,6 +185,7 @@ impl ExtensionFunction {
184185
name.clone(),
185186
1,
186187
args.len(),
188+
None, // evaluator will add the source location later
187189
)),
188190
}),
189191
None,
@@ -208,6 +210,7 @@ impl ExtensionFunction {
208210
name.clone(),
209211
1,
210212
args.len(),
213+
None, // evaluator will add the source location later
211214
)),
212215
}),
213216
Some(return_type),
@@ -234,6 +237,7 @@ impl ExtensionFunction {
234237
name.clone(),
235238
2,
236239
args.len(),
240+
None, // evaluator will add the source location later
237241
)),
238242
}),
239243
Some(return_type),
@@ -263,6 +267,7 @@ impl ExtensionFunction {
263267
name.clone(),
264268
3,
265269
args.len(),
270+
None, // evaluator will add the source location later
266271
)),
267272
}),
268273
Some(return_type),

cedar-policy-core/src/ast/restricted_expr.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ impl<'a> Hash for RestrictedExprShapeOnly<'a> {
600600

601601
/// Error when constructing a restricted expression from unrestricted
602602
603-
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
603+
#[derive(Debug, Clone, PartialEq, Eq, Error)]
604604
pub enum RestrictedExprError {
605605
/// An expression was expected to be a "restricted" expression, but contained
606606
/// a feature that is not allowed in restricted expressions. The `feature`
@@ -616,6 +616,26 @@ pub enum RestrictedExprError {
616616
},
617617
}
618618

619+
// custom impl of `Diagnostic`: take location info from the embedded subexpression
620+
impl Diagnostic for RestrictedExprError {
621+
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
622+
match self {
623+
Self::InvalidRestrictedExpression { expr, .. } => expr.source_loc().map(|loc| {
624+
Box::new(std::iter::once(miette::LabeledSpan::underline(loc.span)))
625+
as Box<dyn Iterator<Item = _>>
626+
}),
627+
}
628+
}
629+
630+
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
631+
match self {
632+
Self::InvalidRestrictedExpression { expr, .. } => expr
633+
.source_loc()
634+
.map(|loc| &loc.src as &dyn miette::SourceCode),
635+
}
636+
}
637+
}
638+
619639
/// Errors possible from `RestrictedExpr::from_str()`
620640
#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
621641
pub enum RestrictedExprParseError {

cedar-policy-core/src/evaluator.rs

Lines changed: 745 additions & 373 deletions
Large diffs are not rendered by default.

cedar-policy-core/src/evaluator/err.rs

Lines changed: 96 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
*/
1616

1717
use crate::ast::*;
18+
use crate::parser::Loc;
1819
use itertools::Itertools;
19-
use miette::Diagnostic;
20+
use miette::{Diagnostic, LabeledSpan};
2021
use nonempty::{nonempty, NonEmpty};
2122
use smol_str::SmolStr;
2223
use std::sync::Arc;
@@ -30,10 +31,13 @@ pub struct EvaluationError {
3031
error_kind: EvaluationErrorKind,
3132
/// Optional advice on how to fix the error
3233
advice: Option<String>,
34+
/// Source location of the error. (This overrides other sources if present,
35+
/// but if this is `None`, we'll check for location info in the
36+
/// `.error_kind`.)
37+
source_loc: Option<Loc>,
3338
}
3439

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

51+
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
52+
self.source_loc
53+
.as_ref()
54+
.map(|loc| &loc.src as &dyn miette::SourceCode)
55+
.or_else(|| self.error_kind.source_code())
56+
}
57+
58+
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
59+
self.source_loc
60+
.as_ref()
61+
.map(|loc| {
62+
Box::new(std::iter::once(LabeledSpan::underline(loc.span)))
63+
as Box<dyn Iterator<Item = _>>
64+
})
65+
.or_else(|| self.error_kind.labels())
66+
}
67+
4768
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
4869
self.error_kind.code()
4970
}
@@ -56,14 +77,6 @@ impl Diagnostic for EvaluationError {
5677
self.error_kind.url()
5778
}
5879

59-
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
60-
self.error_kind.source_code()
61-
}
62-
63-
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
64-
self.error_kind.labels()
65-
}
66-
6780
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
6881
self.error_kind.diagnostic_source()
6982
}
@@ -79,137 +92,188 @@ impl EvaluationError {
7992
&self.error_kind
8093
}
8194

95+
/// Extract the source location of the error, if one is attached
96+
pub fn source_loc(&self) -> Option<&Loc> {
97+
self.source_loc.as_ref()
98+
}
99+
100+
/// Extract the advice attached to the error, if any
101+
pub fn advice(&self) -> Option<&str> {
102+
self.advice.as_deref()
103+
}
104+
82105
/// Set the advice field of an error
83106
pub fn set_advice(&mut self, advice: String) {
84107
self.advice = Some(advice);
85108
}
86109

110+
/// Return the `EvaluationError`, but with the new `source_loc` (or `None`).
111+
pub(crate) fn with_maybe_source_loc(self, source_loc: Option<Loc>) -> Self {
112+
Self { source_loc, ..self }
113+
}
114+
87115
/// Construct a [`EntityDoesNotExist`] error
88-
pub(crate) fn entity_does_not_exist(euid: Arc<EntityUID>) -> Self {
116+
pub(crate) fn entity_does_not_exist(euid: Arc<EntityUID>, source_loc: Option<Loc>) -> Self {
89117
Self {
90118
error_kind: EvaluationErrorKind::EntityDoesNotExist(euid),
91119
advice: None,
120+
source_loc,
92121
}
93122
}
94123

95124
/// Construct a [`EntityAttrDoesNotExist`] error
96-
pub(crate) fn entity_attr_does_not_exist(entity: Arc<EntityUID>, attr: SmolStr) -> Self {
125+
pub(crate) fn entity_attr_does_not_exist(
126+
entity: Arc<EntityUID>,
127+
attr: SmolStr,
128+
source_loc: Option<Loc>,
129+
) -> Self {
97130
Self {
98131
error_kind: EvaluationErrorKind::EntityAttrDoesNotExist { entity, attr },
99132
advice: None,
133+
source_loc,
100134
}
101135
}
102136

103137
/// Construct a [`UnspecifiedEntityAccess`] error
104-
pub(crate) fn unspecified_entity_access(attr: SmolStr) -> Self {
138+
pub(crate) fn unspecified_entity_access(attr: SmolStr, source_loc: Option<Loc>) -> Self {
105139
Self {
106140
error_kind: EvaluationErrorKind::UnspecifiedEntityAccess(attr),
107141
advice: None,
142+
source_loc,
108143
}
109144
}
110145

111146
/// Construct a [`RecordAttrDoesNotExist`] error
112-
pub(crate) fn record_attr_does_not_exist(attr: SmolStr, alternatives: Vec<SmolStr>) -> Self {
147+
pub(crate) fn record_attr_does_not_exist(
148+
attr: SmolStr,
149+
alternatives: Vec<SmolStr>,
150+
source_loc: Option<Loc>,
151+
) -> Self {
113152
Self {
114153
error_kind: EvaluationErrorKind::RecordAttrDoesNotExist(attr, alternatives),
115154
advice: None,
155+
source_loc,
116156
}
117157
}
118158

119159
/// Construct a [`TypeError`] error
120-
pub(crate) fn type_error(expected: NonEmpty<Type>, actual: Type) -> Self {
160+
pub(crate) fn type_error(expected: NonEmpty<Type>, actual: &Value) -> Self {
121161
Self {
122-
error_kind: EvaluationErrorKind::TypeError { expected, actual },
162+
error_kind: EvaluationErrorKind::TypeError {
163+
expected,
164+
actual: actual.type_of(),
165+
},
123166
advice: None,
167+
source_loc: actual.source_loc().cloned(),
124168
}
125169
}
126170

127-
pub(crate) fn type_error_single(expected: Type, actual: Type) -> Self {
171+
pub(crate) fn type_error_single(expected: Type, actual: &Value) -> Self {
128172
Self::type_error(nonempty![expected], actual)
129173
}
130174

131175
/// Construct a [`TypeError`] error with the advice field set
132176
pub(crate) fn type_error_with_advice(
133177
expected: NonEmpty<Type>,
134-
actual: Type,
178+
actual: &Value,
135179
advice: String,
136180
) -> Self {
137181
Self {
138-
error_kind: EvaluationErrorKind::TypeError { expected, actual },
182+
error_kind: EvaluationErrorKind::TypeError {
183+
expected,
184+
actual: actual.type_of(),
185+
},
139186
advice: Some(advice),
187+
source_loc: actual.source_loc().cloned(),
140188
}
141189
}
142190

143191
pub(crate) fn type_error_with_advice_single(
144192
expected: Type,
145-
actual: Type,
193+
actual: &Value,
146194
advice: String,
147195
) -> Self {
148196
Self::type_error_with_advice(nonempty![expected], actual, advice)
149197
}
150198

151199
/// Construct a [`WrongNumArguments`] error
152-
pub(crate) fn wrong_num_arguments(function_name: Name, expected: usize, actual: usize) -> Self {
200+
pub(crate) fn wrong_num_arguments(
201+
function_name: Name,
202+
expected: usize,
203+
actual: usize,
204+
source_loc: Option<Loc>,
205+
) -> Self {
153206
Self {
154207
error_kind: EvaluationErrorKind::WrongNumArguments {
155208
function_name,
156209
expected,
157210
actual,
158211
},
159212
advice: None,
213+
source_loc,
160214
}
161215
}
162216

163217
/// Construct a [`UnlinkedSlot`] error
164-
pub(crate) fn unlinked_slot(id: SlotId) -> Self {
218+
pub(crate) fn unlinked_slot(id: SlotId, source_loc: Option<Loc>) -> Self {
165219
Self {
166220
error_kind: EvaluationErrorKind::UnlinkedSlot(id),
167221
advice: None,
222+
source_loc,
168223
}
169224
}
170225

171226
/// Construct a [`FailedExtensionFunctionApplication`] error
172-
pub(crate) fn failed_extension_function_application(extension_name: Name, msg: String) -> Self {
227+
pub(crate) fn failed_extension_function_application(
228+
extension_name: Name,
229+
msg: String,
230+
source_loc: Option<Loc>,
231+
) -> Self {
173232
Self {
174233
error_kind: EvaluationErrorKind::FailedExtensionFunctionApplication {
175234
extension_name,
176235
msg,
177236
},
178237
advice: None,
238+
source_loc,
179239
}
180240
}
181241

182242
/// Construct a [`NonValue`] error
183243
pub(crate) fn non_value(e: Expr) -> Self {
244+
let source_loc = e.source_loc().cloned();
184245
Self {
185246
error_kind: EvaluationErrorKind::NonValue(e),
186247
advice: Some("consider using the partial evaluation APIs".into()),
248+
source_loc,
187249
}
188250
}
189251

190252
/// Construct a [`RecursionLimit`] error
191-
pub(crate) fn recursion_limit() -> Self {
253+
pub(crate) fn recursion_limit(source_loc: Option<Loc>) -> Self {
192254
Self {
193255
error_kind: EvaluationErrorKind::RecursionLimit,
194256
advice: None,
257+
source_loc,
195258
}
196259
}
197-
}
198260

199-
impl From<crate::extensions::ExtensionFunctionLookupError> for EvaluationError {
200-
fn from(err: crate::extensions::ExtensionFunctionLookupError) -> Self {
261+
pub(crate) fn extension_function_lookup(
262+
err: crate::extensions::ExtensionFunctionLookupError,
263+
source_loc: Option<Loc>,
264+
) -> Self {
201265
Self {
202266
error_kind: err.into(),
203267
advice: None,
268+
source_loc,
204269
}
205270
}
206-
}
207271

208-
impl From<IntegerOverflowError> for EvaluationError {
209-
fn from(err: IntegerOverflowError) -> Self {
272+
pub(crate) fn integer_overflow(err: IntegerOverflowError, source_loc: Option<Loc>) -> Self {
210273
Self {
211274
error_kind: err.into(),
212275
advice: None,
276+
source_loc,
213277
}
214278
}
215279
}
@@ -219,6 +283,7 @@ impl From<RestrictedExprError> for EvaluationError {
219283
Self {
220284
error_kind: err.into(),
221285
advice: None,
286+
source_loc: None, // defer to the source information embedded in the `RestrictedExprError` and thus stored in `error_kind`
222287
}
223288
}
224289
}

0 commit comments

Comments
 (0)