1use std::fmt;
8
9#[derive(Debug, Clone, thiserror::Error)]
14#[non_exhaustive]
15pub enum ExpressionErrorKind {
16 #[error("Undefined variable: '{name}'.{suggestion}")]
18 UndefinedVariable { name: String, suggestion: String },
19
20 #[error("Unknown function: '{name}'")]
22 UnknownFunction { name: String },
23
24 #[error("{message}")]
26 TypeError { message: String },
27
28 #[error("Integer overflow: result is outside the 64-bit signed range")]
30 IntegerOverflow,
31
32 #[error("{op} by zero")]
34 DivisionByZero { op: &'static str },
35
36 #[error("{message}")]
38 FloatError { message: String },
39
40 #[error("{message}")]
42 IndexOutOfBounds { message: String },
43
44 #[error("Expression memory usage ({used} bytes) exceeded limit ({limit} bytes)")]
46 MemoryLimitExceeded { used: usize, limit: usize },
47
48 #[error("Expression operation count ({count}) exceeded limit ({limit})")]
50 OperationLimitExceeded { count: usize, limit: usize },
51
52 #[error("Expression nesting depth ({depth}) exceeded limit ({limit})")]
59 ExpressionTooDeep { depth: usize, limit: usize },
60
61 #[error("{feature}")]
63 UnsupportedSyntax { feature: String },
64
65 #[error("{0}")]
67 ExplicitFail(String),
68
69 #[error("{0}")]
71 ParseError(String),
72
73 #[error("{0}")]
75 Other(String),
76}
77
78#[derive(Debug, Clone)]
86pub struct ExpressionError {
87 inner: Box<ExpressionErrorInner>,
88}
89
90#[derive(Debug, Clone)]
91struct ExpressionErrorInner {
92 kind: ExpressionErrorKind,
93 expr: Option<String>,
94 col_offset: Option<usize>,
95 end_col_offset: Option<usize>,
96 caret_offset: Option<usize>,
97 sub_errors: Option<Vec<ExpressionError>>,
98}
99
100impl ExpressionError {
101 pub fn from_kind(kind: ExpressionErrorKind) -> Self {
103 Self {
104 inner: Box::new(ExpressionErrorInner {
105 kind,
106 expr: None,
107 col_offset: None,
108 end_col_offset: None,
109 caret_offset: None,
110 sub_errors: None,
111 }),
112 }
113 }
114
115 pub fn new(message: impl Into<String>) -> Self {
121 Self::from_kind(ExpressionErrorKind::Other(message.into()))
122 }
123
124 pub fn integer_overflow() -> Self {
128 Self::from_kind(ExpressionErrorKind::IntegerOverflow)
129 }
130
131 pub fn division_by_zero(op: &'static str) -> Self {
133 Self::from_kind(ExpressionErrorKind::DivisionByZero { op })
134 }
135
136 pub fn float_error(message: impl Into<String>) -> Self {
138 Self::from_kind(ExpressionErrorKind::FloatError {
139 message: message.into(),
140 })
141 }
142
143 pub fn type_error(message: impl Into<String>) -> Self {
145 Self::from_kind(ExpressionErrorKind::TypeError {
146 message: message.into(),
147 })
148 }
149
150 pub fn index_out_of_bounds(message: impl Into<String>) -> Self {
152 Self::from_kind(ExpressionErrorKind::IndexOutOfBounds {
153 message: message.into(),
154 })
155 }
156
157 pub fn unsupported(feature: impl Into<String>) -> Self {
159 Self::from_kind(ExpressionErrorKind::UnsupportedSyntax {
160 feature: feature.into(),
161 })
162 }
163
164 pub fn explicit_fail(message: impl Into<String>) -> Self {
166 Self::from_kind(ExpressionErrorKind::ExplicitFail(message.into()))
167 }
168
169 pub fn parse_error(message: impl Into<String>) -> Self {
171 Self::from_kind(ExpressionErrorKind::ParseError(message.into()))
172 }
173
174 pub fn expression_too_deep(depth: usize, limit: usize) -> Self {
176 Self::from_kind(ExpressionErrorKind::ExpressionTooDeep { depth, limit })
177 }
178
179 pub fn kind(&self) -> &ExpressionErrorKind {
181 &self.inner.kind
182 }
183
184 pub fn message(&self) -> String {
186 self.inner.kind.to_string()
187 }
188
189 pub fn sub_errors(&self) -> &[ExpressionError] {
191 match &self.inner.sub_errors {
192 Some(v) => v.as_slice(),
193 None => &[],
194 }
195 }
196
197 pub fn with_sub_errors(mut self, sub_errors: Vec<ExpressionError>) -> Self {
199 if !sub_errors.is_empty() {
200 self.inner.sub_errors = Some(sub_errors);
201 }
202 self
203 }
204
205 pub fn expr(&self) -> Option<&str> {
207 self.inner.expr.as_deref()
208 }
209
210 pub fn col_offset(&self) -> Option<usize> {
212 self.inner.col_offset
213 }
214
215 pub fn end_col_offset(&self) -> Option<usize> {
217 self.inner.end_col_offset
218 }
219
220 pub fn caret_offset(&self) -> Option<usize> {
222 self.inner.caret_offset
223 }
224
225 #[must_use]
227 pub fn with_node(mut self, expr_source: &str, node: &ruff_python_ast::Expr) -> Self {
228 use ruff_text_size::Ranged;
229 if self.inner.expr.is_some() {
230 return self;
231 }
232 self.inner.expr = Some(expr_source.to_string());
233 let range = node.range();
234 self.inner.col_offset = Some(range.start().to_usize());
235 self.inner.end_col_offset = Some(range.end().to_usize());
236 self.inner.caret_offset = Some(compute_caret_offset(expr_source, node));
237 self
238 }
239
240 #[must_use]
242 pub fn with_span(mut self, expr_source: &str, col: usize, end_col: usize) -> Self {
243 if self.inner.expr.is_some() {
244 return self;
245 }
246 self.inner.expr = Some(expr_source.to_string());
247 self.inner.col_offset = Some(col);
248 self.inner.end_col_offset = Some(end_col);
249 self
250 }
251
252 pub fn set_source_span(
255 &mut self,
256 expr_source: &str,
257 col: usize,
258 end_col: usize,
259 caret_offset: usize,
260 ) {
261 self.inner.expr = Some(expr_source.to_string());
262 self.inner.col_offset = Some(col);
263 self.inner.end_col_offset = Some(end_col);
264 self.inner.caret_offset = Some(caret_offset);
265 }
266
267 pub fn message_with_expr_prefix(&self, prefix: &str) -> String {
278 let (Some(expr), Some(col), Some(end_col)) = (
279 &self.inner.expr,
280 self.inner.col_offset,
281 self.inner.end_col_offset,
282 ) else {
283 return self.to_string();
284 };
285 if expr.contains('\n') {
286 return self.to_string();
287 }
288 let msg = self.message();
289 let mut out = msg;
290 out.push_str("\n ");
291 out.push_str(prefix);
292 out.push_str(expr);
293 out.push_str("\n ");
294 let _ = write_caret_line(
295 &mut out,
296 col + prefix.len(),
297 end_col + prefix.len(),
298 self.inner.caret_offset.unwrap_or(0),
299 );
300 out
301 }
302}
303
304impl fmt::Display for ExpressionError {
305 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306 write!(f, "{}", self.inner.kind)?;
307 if let (Some(expr), Some(col), Some(end_col)) = (
308 &self.inner.expr,
309 self.inner.col_offset,
310 self.inner.end_col_offset,
311 ) {
312 let is_multiline = expr.contains('\n');
313 let (col, end_col) = if is_multiline {
315 (col.saturating_sub(1), end_col.saturating_sub(1))
316 } else {
317 (col, end_col)
318 };
319
320 let (expr_line, line_col, line_end_col) = if is_multiline {
322 let mut pos = 0;
323 let mut found_line = expr.as_str();
324 let mut line_start = 0;
325 for line in expr.split('\n') {
326 if pos + line.len() >= col {
327 found_line = line;
328 line_start = pos;
329 break;
330 }
331 pos += line.len() + 1; }
333 let lc = col - line_start;
334 let lec = if end_col > line_start {
335 (end_col - line_start).min(found_line.len())
336 } else {
337 lc + 1
338 };
339 (found_line, lc, lec)
340 } else {
341 (expr.as_str(), col, end_col)
342 };
343
344 write!(f, "\n {expr_line}\n ")?;
345 write_caret_line(
346 f,
347 line_col,
348 line_end_col,
349 self.inner.caret_offset.unwrap_or(0),
350 )?;
351 }
352 Ok(())
353 }
354}
355
356impl std::error::Error for ExpressionError {
357 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
358 Some(&self.inner.kind)
359 }
360}
361
362pub(crate) fn write_caret_line(
378 w: &mut dyn std::fmt::Write,
379 col: usize,
380 end_col: usize,
381 caret_offset: usize,
382) -> std::fmt::Result {
383 let span_len = end_col.saturating_sub(col);
384 for _ in 0..col {
385 w.write_char(' ')?;
386 }
387 if span_len > 1 {
388 let caret_idx = caret_offset.min(span_len.saturating_sub(1));
389 for _ in 0..caret_idx {
390 w.write_char('~')?;
391 }
392 w.write_char('^')?;
393 for _ in 0..span_len.saturating_sub(caret_idx + 1) {
394 w.write_char('~')?;
395 }
396 } else {
397 w.write_char('^')?;
398 }
399 Ok(())
400}
401
402fn compute_caret_offset(expr: &str, node: &ruff_python_ast::Expr) -> usize {
404 use ruff_python_ast as ast;
405 use ruff_text_size::Ranged;
406 match node {
407 ast::Expr::BinOp(b) => {
408 let left_end = b.left.range().end().to_usize();
409 let right_start = b.right.range().start().to_usize();
410 let node_start = node.range().start().to_usize();
411 let bytes = expr.as_bytes();
413 let mut i = right_start.saturating_sub(1);
414 while i > left_end
415 && i < bytes.len()
416 && (bytes[i] == b' ' || bytes[i] == b'\t' || bytes[i] == b'(')
417 {
418 i -= 1;
419 }
420 if i > left_end
422 && i < bytes.len()
423 && i >= 1
424 && (bytes[i - 1..=i] == *b"**" || bytes[i - 1..=i] == *b"//")
425 {
426 return (i - 1) - node_start;
427 }
428 if i >= left_end && i < bytes.len() {
429 i - node_start
430 } else {
431 0
432 }
433 }
434 ast::Expr::Attribute(a) => {
435 let value_end = a.value.range().end().to_usize();
436 let node_start = node.range().start().to_usize();
437 (value_end + 1).saturating_sub(node_start) }
439 ast::Expr::Call(c) => {
440 if let ast::Expr::Attribute(a) = &*c.func {
441 let value_end = a.value.range().end().to_usize();
442 let node_start = node.range().start().to_usize();
443 (value_end + 1).saturating_sub(node_start)
444 } else {
445 0
446 }
447 }
448 ast::Expr::Subscript(s) => {
449 let value_end = s.value.range().end().to_usize();
450 let node_start = node.range().start().to_usize();
451 value_end.saturating_sub(node_start)
452 }
453 _ => 0,
454 }
455}