Skip to main content

polars_error/
lib.rs

1pub mod constants;
2mod warning;
3
4use std::borrow::Cow;
5use std::collections::TryReserveError;
6use std::convert::Infallible;
7use std::error::Error;
8use std::fmt::{self, Display, Formatter, Write};
9use std::ops::Deref;
10use std::sync::{Arc, LazyLock};
11use std::{env, io};
12pub mod signals;
13
14pub use warning::*;
15
16#[cfg(feature = "python")]
17mod python;
18
19enum ErrorStrategy {
20    Panic,
21    WithBacktrace,
22    Normal,
23}
24
25static ERROR_STRATEGY: LazyLock<ErrorStrategy> = LazyLock::new(|| {
26    if env::var("POLARS_PANIC_ON_ERR").as_deref() == Ok("1") {
27        ErrorStrategy::Panic
28    } else if env::var("POLARS_BACKTRACE_IN_ERR").as_deref() == Ok("1") {
29        ErrorStrategy::WithBacktrace
30    } else {
31        ErrorStrategy::Normal
32    }
33});
34
35#[derive(Debug, Clone)]
36pub struct ErrString(Cow<'static, str>);
37
38impl ErrString {
39    pub const fn new_static(s: &'static str) -> Self {
40        Self(Cow::Borrowed(s))
41    }
42}
43
44impl<T> From<T> for ErrString
45where
46    T: Into<Cow<'static, str>>,
47{
48    #[track_caller]
49    fn from(msg: T) -> Self {
50        match &*ERROR_STRATEGY {
51            ErrorStrategy::Panic => panic!("{}", msg.into()),
52            ErrorStrategy::WithBacktrace => ErrString(Cow::Owned(format!(
53                "{}\n\nRust backtrace:\n{}",
54                msg.into(),
55                std::backtrace::Backtrace::force_capture()
56            ))),
57            ErrorStrategy::Normal => ErrString(msg.into()),
58        }
59    }
60}
61
62impl AsRef<str> for ErrString {
63    fn as_ref(&self) -> &str {
64        &self.0
65    }
66}
67
68impl Deref for ErrString {
69    type Target = str;
70
71    fn deref(&self) -> &Self::Target {
72        &self.0
73    }
74}
75
76impl Display for ErrString {
77    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
78        write!(f, "{}", self.0)
79    }
80}
81
82#[derive(Debug, Clone)]
83pub enum PolarsError {
84    AssertionError(ErrString),
85    ColumnNotFound(ErrString),
86    ComputeError(ErrString),
87    Duplicate(ErrString),
88    InvalidOperation(ErrString),
89    IO {
90        error: Arc<io::Error>,
91        msg: Option<ErrString>,
92    },
93    NoData(ErrString),
94    OutOfBounds(ErrString),
95    SchemaFieldNotFound(ErrString),
96    SchemaMismatch(ErrString),
97    ShapeMismatch(ErrString),
98    SQLInterface(ErrString),
99    SQLSyntax(ErrString),
100    StringCacheMismatch(ErrString),
101    StructFieldNotFound(ErrString),
102    Context {
103        error: Box<PolarsError>,
104        msg: ErrString,
105    },
106    ExprContext {
107        error: Box<PolarsError>,
108        expr: ErrString,
109    },
110    #[cfg(feature = "python")]
111    Python {
112        error: python::PyErrWrap,
113    },
114}
115
116impl Error for PolarsError {
117    fn source(&self) -> Option<&(dyn Error + 'static)> {
118        match self {
119            PolarsError::AssertionError(_)
120            | PolarsError::ColumnNotFound(_)
121            | PolarsError::ComputeError(_)
122            | PolarsError::Duplicate(_)
123            | PolarsError::InvalidOperation(_)
124            | PolarsError::NoData(_)
125            | PolarsError::OutOfBounds(_)
126            | PolarsError::SchemaFieldNotFound(_)
127            | PolarsError::SchemaMismatch(_)
128            | PolarsError::ShapeMismatch(_)
129            | PolarsError::SQLInterface(_)
130            | PolarsError::SQLSyntax(_)
131            | PolarsError::StringCacheMismatch(_)
132            | PolarsError::StructFieldNotFound(_) => None,
133            PolarsError::IO { error, .. } => Some(error.as_ref()),
134            PolarsError::Context { error, .. } => Some(error.as_ref()),
135            PolarsError::ExprContext { error, .. } => Some(error.as_ref()),
136            #[cfg(feature = "python")]
137            PolarsError::Python { error } => error.deref().source(),
138        }
139    }
140}
141
142impl Display for PolarsError {
143    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
144        use PolarsError::*;
145        match self {
146            ComputeError(msg)
147            | InvalidOperation(msg)
148            | OutOfBounds(msg)
149            | SchemaMismatch(msg)
150            | SQLInterface(msg)
151            | SQLSyntax(msg) => write!(f, "{msg}"),
152
153            AssertionError(msg) => write!(f, "assertion failed: {msg}"),
154            ColumnNotFound(msg) => write!(f, "not found: {msg}"),
155            Duplicate(msg) => write!(f, "duplicate: {msg}"),
156            IO { error, msg } => match msg {
157                Some(m) => write!(f, "{m}"),
158                None => write!(f, "{error}"),
159            },
160            NoData(msg) => write!(f, "no data: {msg}"),
161            SchemaFieldNotFound(msg) => write!(f, "field not found: {msg}"),
162            ShapeMismatch(msg) => write!(f, "lengths don't match: {msg}"),
163            StringCacheMismatch(msg) => write!(f, "string caches don't match: {msg}"),
164            StructFieldNotFound(msg) => write!(f, "field not found: {msg}"),
165            Context { error, msg } => write!(f, "{error}: {msg}"),
166            ExprContext { error, expr: _ } => write!(f, "{error}"),
167            #[cfg(feature = "python")]
168            Python { error } => write!(f, "python: {error}"),
169        }
170    }
171}
172
173impl From<io::Error> for PolarsError {
174    fn from(mut value: io::Error) -> Self {
175        if let Some(polars_err) = value
176            .get_mut()
177            .and_then(|e| e.downcast_mut::<PolarsError>())
178        {
179            std::mem::replace(
180                polars_err,
181                PolarsError::ComputeError(ErrString::new_static("")),
182            )
183        } else {
184            PolarsError::IO {
185                error: Arc::new(value),
186                msg: None,
187            }
188        }
189    }
190}
191
192impl From<PolarsError> for io::Error {
193    fn from(value: PolarsError) -> Self {
194        io::Error::other(value)
195    }
196}
197
198#[cfg(feature = "regex")]
199impl From<regex::Error> for PolarsError {
200    fn from(err: regex::Error) -> Self {
201        PolarsError::ComputeError(format!("regex error: {err}").into())
202    }
203}
204
205#[cfg(feature = "avro-schema")]
206impl From<avro_schema::error::Error> for PolarsError {
207    fn from(value: avro_schema::error::Error) -> Self {
208        polars_err!(ComputeError: "avro-error: {}", value)
209    }
210}
211
212impl From<simdutf8::basic::Utf8Error> for PolarsError {
213    fn from(value: simdutf8::basic::Utf8Error) -> Self {
214        polars_err!(ComputeError: "invalid utf8: {}", value)
215    }
216}
217#[cfg(feature = "arrow-format")]
218impl From<arrow_format::ipc::planus::Error> for PolarsError {
219    fn from(err: arrow_format::ipc::planus::Error) -> Self {
220        polars_err!(ComputeError: "parquet error: {err:?}")
221    }
222}
223
224impl From<TryReserveError> for PolarsError {
225    fn from(value: TryReserveError) -> Self {
226        polars_err!(ComputeError: "OOM: {}", value)
227    }
228}
229
230impl From<Infallible> for PolarsError {
231    fn from(_: Infallible) -> Self {
232        unreachable!()
233    }
234}
235
236pub type PolarsResult<T> = Result<T, PolarsError>;
237
238impl PolarsError {
239    pub fn context_trace(self) -> Self {
240        use PolarsError::*;
241        if !matches!(self, Context { .. } | ExprContext { .. }) {
242            return self;
243        }
244
245        let mut context_msgs = Vec::new();
246        let mut context_exprs = Vec::new();
247        let mut error = self;
248        loop {
249            match error {
250                Context { error: e, msg } => {
251                    context_msgs.push(msg);
252                    error = *e;
253                },
254
255                ExprContext { error: e, expr } => {
256                    context_exprs.push(expr);
257                    error = *e;
258                },
259
260                e => {
261                    error = e;
262                    break;
263                },
264            }
265        }
266
267        error.wrap_msg(|msg| {
268            let mut out = msg.to_string();
269            if !context_exprs.is_empty() {
270                let first = context_exprs.first().unwrap();
271                let last = context_exprs.last().unwrap();
272                writeln!(
273                    &mut out,
274                    "\n\nThis error occurred in the following expression:"
275                )
276                .unwrap();
277                writeln!(&mut out, "\t{last}").unwrap();
278                if first.0 != last.0 {
279                    writeln!(&mut out, "while evaluating this larger expression:").unwrap();
280                    writeln!(&mut out, "\t{first}").unwrap();
281                }
282            }
283
284            if !context_msgs.is_empty() {
285                writeln!(
286                    &mut out,
287                    "\n\nThis error occurred with the following context stack:"
288                )
289                .unwrap();
290                for (i, m) in context_msgs.into_iter().rev().enumerate() {
291                    writeln!(&mut out, "\t[{}] {}", i + 1, m).unwrap();
292                }
293            }
294            out
295        })
296    }
297
298    pub fn wrap_msg<F: FnOnce(&str) -> String>(&self, func: F) -> Self {
299        use PolarsError::*;
300        match self {
301            AssertionError(msg) => AssertionError(func(msg).into()),
302            ColumnNotFound(msg) => ColumnNotFound(func(msg).into()),
303            ComputeError(msg) => ComputeError(func(msg).into()),
304            Duplicate(msg) => Duplicate(func(msg).into()),
305            InvalidOperation(msg) => InvalidOperation(func(msg).into()),
306            IO { error, msg } => {
307                let msg = match msg {
308                    Some(msg) => func(msg),
309                    None => func(&format!("{error}")),
310                };
311                IO {
312                    error: error.clone(),
313                    msg: Some(msg.into()),
314                }
315            },
316            NoData(msg) => NoData(func(msg).into()),
317            OutOfBounds(msg) => OutOfBounds(func(msg).into()),
318            SchemaFieldNotFound(msg) => SchemaFieldNotFound(func(msg).into()),
319            SchemaMismatch(msg) => SchemaMismatch(func(msg).into()),
320            ShapeMismatch(msg) => ShapeMismatch(func(msg).into()),
321            StringCacheMismatch(msg) => StringCacheMismatch(func(msg).into()),
322            StructFieldNotFound(msg) => StructFieldNotFound(func(msg).into()),
323            SQLInterface(msg) => SQLInterface(func(msg).into()),
324            SQLSyntax(msg) => SQLSyntax(func(msg).into()),
325            Context {
326                error,
327                msg: context_msg,
328            } => Context {
329                error: Box::new(error.wrap_msg(func)),
330                msg: context_msg.clone(),
331            },
332            ExprContext { error, expr } => ExprContext {
333                error: Box::new(error.wrap_msg(func)),
334                expr: expr.clone(),
335            },
336            #[cfg(feature = "python")]
337            Python { error } => pyo3::Python::attach(|py| {
338                use pyo3::types::{PyAnyMethods, PyStringMethods};
339                use pyo3::{IntoPyObject, PyErr};
340
341                let value = error.value(py);
342
343                let msg = if let Ok(s) = value.str() {
344                    func(&s.to_string_lossy())
345                } else {
346                    func("<exception str() failed>")
347                };
348
349                let cls = value.get_type();
350
351                let out = PyErr::from_type(cls, (msg,));
352
353                let out = if let Ok(out_with_traceback) = (|| {
354                    out.clone_ref(py)
355                        .into_pyobject(py)?
356                        .getattr("with_traceback")
357                        .unwrap()
358                        .call1((value.getattr("__traceback__").unwrap(),))
359                })() {
360                    PyErr::from_value(out_with_traceback)
361                } else {
362                    out
363                };
364
365                Python {
366                    error: python::PyErrWrap(out),
367                }
368            }),
369        }
370    }
371
372    pub fn context(self, msg: ErrString) -> Self {
373        PolarsError::Context {
374            error: Box::new(self),
375            msg,
376        }
377    }
378
379    pub fn remove_context(mut self) -> Self {
380        while let Self::Context { error, .. } = self {
381            self = *error;
382        }
383        self
384    }
385
386    pub fn with_expr_context(self, expr: ErrString) -> Self {
387        PolarsError::ExprContext {
388            error: Box::new(self),
389            expr,
390        }
391    }
392}
393
394pub fn map_err<E: Error>(error: E) -> PolarsError {
395    PolarsError::ComputeError(format!("{error}").into())
396}
397
398#[macro_export]
399macro_rules! polars_err {
400    ($variant:ident: format!($_:tt) $(, _:tt)* $(,)?) => {
401        const { panic!("remove unnecessary format! from polars_(bail|err)! macro") }
402    };
403    ($variant:ident: $fmt:literal $(,)?) => {{
404        if const { $crate::__private::has_brace($fmt) } {
405            $crate::__private::must_use(
406                $crate::PolarsError::$variant(format!($fmt).into())
407            )
408        } else {
409            const {
410                $crate::__private::must_use(
411                    $crate::PolarsError::$variant($crate::ErrString::new_static($fmt))
412                )
413            }
414        }
415    }};
416    ($variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {
417        $crate::__private::must_use(
418            $crate::PolarsError::$variant(format!($fmt, $($arg),*).into())
419        )
420    };
421    ($variant:ident: $fmt:literal $(, $arg:expr)*, hint = $hint:literal) => {
422        $crate::__private::must_use(
423            $crate::PolarsError::$variant(format!(concat_str!($fmt, "\n\nHint: ", $hint), $($arg),*).into())
424        )
425    };
426    ($variant:ident: $err:expr $(,)?) => {
427        $crate::__private::must_use(
428            $crate::PolarsError::$variant($err.into())
429        )
430    };
431    (expr = $expr:expr, $variant:ident: $err:expr $(,)?) => {
432        $crate::__private::must_use(
433            $crate::PolarsError::$variant(
434                format!("{}\n\nError originated in expression: '{:?}'", $err, $expr).into()
435            )
436        )
437    };
438    (expr = $expr:expr, $variant:ident: $fmt:literal, $($arg:tt)+) => {
439        polars_err!(expr = $expr, $variant: format!($fmt, $($arg)+))
440    };
441    (op = $op:expr, got = $arg:expr, expected = $expected:expr) => {
442        $crate::polars_err!(
443            InvalidOperation: "{} operation not supported for dtype `{}` (expected: {})",
444            $op, $arg, $expected
445        )
446    };
447    (opq = $op:ident, got = $arg:expr, expected = $expected:expr) => {
448        $crate::polars_err!(
449            op = concat!("`", stringify!($op), "`"), got = $arg, expected = $expected
450        )
451    };
452    (un_impl = $op:ident) => {
453        $crate::polars_err!(
454            InvalidOperation: "{} operation is not implemented.", concat!("`", stringify!($op), "`")
455        )
456    };
457    (op = $op:expr, $arg:expr) => {
458        $crate::polars_err!(
459            InvalidOperation: "{} operation not supported for dtype `{}`", $op, $arg
460        )
461    };
462    (op = $op:expr, $arg:expr, hint = $hint:literal) => {
463        $crate::polars_err!(
464            InvalidOperation: "{} operation not supported for dtype `{}`\n\nHint: {}", $op, $arg, $hint
465        )
466    };
467    (op = $op:expr, $lhs:expr, $rhs:expr) => {
468        $crate::polars_err!(
469            InvalidOperation: "{} operation not supported for dtypes `{}` and `{}`", $op, $lhs, $rhs
470        )
471    };
472    (op = $op:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {
473        $crate::polars_err!(
474            InvalidOperation: "{} operation not supported for dtypes `{}`, `{}` and `{}`", $op, $arg1, $arg2, $arg3
475        )
476    };
477    (opidx = $op:expr, idx = $idx:expr, $arg:expr) => {
478        $crate::polars_err!(
479            InvalidOperation: "`{}` operation not supported for dtype `{}` as argument {}", $op, $arg, $idx
480        )
481    };
482    (oos = $($tt:tt)+) => {
483        $crate::polars_err!(ComputeError: "out-of-spec: {}", $($tt)+)
484    };
485    (nyi = $($tt:tt)+) => {
486        $crate::polars_err!(ComputeError: "not yet implemented: {}", format!($($tt)+) )
487    };
488    (opq = $op:ident, $arg:expr) => {
489        $crate::polars_err!(op = concat!("`", stringify!($op), "`"), $arg)
490    };
491    (opq = $op:ident, $lhs:expr, $rhs:expr) => {
492        $crate::polars_err!(op = stringify!($op), $lhs, $rhs)
493    };
494    (bigidx, ctx = $ctx:expr, size = $size:expr) => {
495        $crate::polars_err!(ComputeError: "\
496{} produces {} rows which is more than maximum allowed pow(2, 32)-1 rows; \
497consider compiling with bigidx feature (pip install polars[rt64])",
498            $ctx,
499            $size,
500        )
501    };
502    (append) => {
503        polars_err!(SchemaMismatch: "cannot append series, data types don't match")
504    };
505    (extend) => {
506        polars_err!(SchemaMismatch: "cannot extend series, data types don't match")
507    };
508    (unpack) => {
509        polars_err!(SchemaMismatch: "cannot unpack series, data types don't match")
510    };
511    (not_in_enum,value=$value:expr,categories=$categories:expr) =>{
512        polars_err!(ComputeError: "value '{}' is not present in Enum: {:?}",$value,$categories)
513    };
514    (string_cache_mismatch) => {
515        polars_err!(StringCacheMismatch: r#"
516cannot compare categoricals coming from different sources, consider setting a global StringCache.
517
518Help: if you're using Python, this may look something like:
519
520    with pl.StringCache():
521        df1 = pl.DataFrame({'a': ['1', '2']}, schema={'a': pl.Categorical})
522        df2 = pl.DataFrame({'a': ['1', '3']}, schema={'a': pl.Categorical})
523        pl.concat([df1, df2])
524
525Alternatively, if the performance cost is acceptable, you could just set:
526
527    import polars as pl
528    pl.enable_string_cache()
529
530on startup."#.trim_start())
531    };
532    (duplicate = $name:expr) => {
533        $crate::polars_err!(Duplicate: "column with name '{}' has more than one occurrence", $name)
534    };
535    (duplicate_field = $name:expr) => {
536        $crate::polars_err!(Duplicate: "multiple fields with name '{}' found", $name)
537    };
538    (col_not_found = $name:expr) => {
539        $crate::polars_err!(ColumnNotFound: "{:?} not found", $name)
540    };
541    (mismatch, col=$name:expr, expected=$expected:expr, found=$found:expr) => {
542        $crate::polars_err!(
543            SchemaMismatch: "data type mismatch for column {}: expected: {}, found: {}",
544            $name,
545            $expected,
546            $found,
547        )
548    };
549    (oob = $idx:expr, $len:expr) => {
550        polars_err!(OutOfBounds: "index {} is out of bounds for sequence of length {}", $idx, $len)
551    };
552    (agg_len = $agg_len:expr, $groups_len:expr) => {
553        polars_err!(
554            ComputeError:
555            "returned aggregation is of different length: {} than the groups length: {}",
556            $agg_len, $groups_len
557        )
558    };
559    (parse_fmt_idk = $dtype:expr) => {
560        polars_err!(
561            ComputeError: "could not find an appropriate format to parse {}s, please define a format",
562            $dtype,
563        )
564    };
565    (length_mismatch = $operation:expr, $lhs:expr, $rhs:expr) => {
566        $crate::polars_err!(
567            ShapeMismatch: "arguments for `{}` have different lengths ({} != {})",
568            $operation, $lhs, $rhs
569        )
570    };
571    (length_mismatch = $operation:expr, $lhs:expr, $rhs:expr, argument = $argument:expr, argument_idx = $argument_idx:expr) => {
572        $crate::polars_err!(
573            ShapeMismatch: "argument {} called '{}' for `{}` have different lengths ({} != {})",
574            $argument_idx, $argument, $operation, $lhs, $rhs
575        )
576    };
577    (invalid_element_use) => {
578        $crate::polars_err!(InvalidOperation: "`element` is not allowed in this context")
579    };
580    (invalid_field_use) => {
581        $crate::polars_err!(InvalidOperation: "`field` is not allowed in this context")
582    };
583    (non_utf8_path) => {
584        $crate::polars_err!(ComputeError: "encountered non UTF-8 path characters")
585    };
586    (assertion_error = $objects:expr, $detail:expr, $lhs:expr, $rhs:expr) => {
587        $crate::polars_err!(
588            AssertionError: "{} are different ({})\n[left]: {}\n[right]: {}",
589            $objects, $detail, $lhs, $rhs
590        )
591    };
592    (to_datetime_tz_mismatch) => {
593        $crate::polars_err!(
594            ComputeError: "`strptime` / `to_datetime` was called with no format and no time zone, but a time zone is part of the data.\n\nThis was previously allowed but led to unpredictable and erroneous results. Give a format string, set a time zone or perform the operation eagerly on a Series instead of on an Expr."
595        )
596    };
597    (item_agg_count_not_one = $n:expr, allow_empty = $allow_empty:expr) => {
598        if $n == 0 && !$allow_empty {
599            polars_err!(ComputeError:
600                "aggregation 'item' expected a single value, got none"
601            )
602        } else if $n > 100 {
603            if $allow_empty {
604                polars_err!(ComputeError: "aggregation 'item' expected no or a single value, got 100+ values")
605            } else {
606                polars_err!(ComputeError: "aggregation 'item' expected a single value, got 100+ values")
607            }
608        } else if $n > 1 {
609            if $allow_empty {
610                polars_err!(ComputeError:
611                    "aggregation 'item' expected no or a single value, got {} values", $n
612                )
613            } else {
614                polars_err!(ComputeError:
615                    "aggregation 'item' expected a single value, got {} values", $n
616                )
617            }
618        } else {
619            unreachable!()
620        }
621    };
622}
623
624#[macro_export]
625macro_rules! polars_bail {
626    ($($tt:tt)+) => {
627        return Err($crate::polars_err!($($tt)+))
628    };
629}
630
631#[macro_export]
632macro_rules! polars_ensure {
633    ($cond:expr, $($tt:tt)+) => {
634        if !$cond {
635            $crate::polars_bail!($($tt)+);
636        }
637    };
638}
639
640#[inline]
641#[cold]
642#[must_use]
643pub fn to_compute_err(err: impl Display) -> PolarsError {
644    PolarsError::ComputeError(err.to_string().into())
645}
646
647#[macro_export]
648macro_rules! feature_gated {
649    ($($feature:literal);*, $content:expr) => {{
650        #[cfg(all($(feature = $feature),*))]
651        {
652            $content
653        }
654        #[cfg(not(all($(feature = $feature),*)))]
655        {
656            panic!("activate '{}' feature", concat!($($feature, ", "),*))
657        }
658    }};
659}
660
661// Not public, referenced by macros only.
662#[doc(hidden)]
663pub mod __private {
664    #[doc(hidden)]
665    #[inline]
666    #[cold]
667    #[must_use]
668    pub const fn must_use(error: crate::PolarsError) -> crate::PolarsError {
669        error
670    }
671
672    pub const fn has_brace(s: &str) -> bool {
673        let bytes = s.as_bytes();
674        let mut i: usize = 0;
675
676        while i < bytes.len() {
677            if bytes[i] == b'{' || bytes[i] == b'}' {
678                return true;
679            }
680
681            i += 1;
682        }
683
684        false
685    }
686}
687
688#[cfg(test)]
689mod tests {
690    use crate::{ErrString, PolarsError};
691
692    #[test]
693    fn test_polars_error_roundtrips_through_std_io_error() {
694        use PolarsError::ComputeError;
695
696        let error_magic = "err_magic_3";
697        let error = ComputeError(ErrString::new_static(error_magic));
698
699        let io_error: std::io::Error = error.into();
700        let error: PolarsError = io_error.into();
701
702        match error {
703            ComputeError(s) if &*s == error_magic => {},
704            e => panic!("error type mismatch: {e}"),
705        };
706    }
707
708    #[test]
709    fn test_polars_error_format_str() {
710        use PolarsError::ComputeError;
711
712        let a = "A";
713
714        match polars_err!(ComputeError: "{a}") {
715            ComputeError(out) if &*out == a => {},
716            e => panic!("{e}"),
717        }
718
719        match polars_err!(ComputeError: a) {
720            ComputeError(out) if &*out == a => {},
721            e => panic!("{e}"),
722        }
723
724        match polars_err!(ComputeError: "{{") {
725            ComputeError(out) if &*out == "{" => {},
726            e => panic!("{e}"),
727        }
728
729        match polars_err!(ComputeError: "}}") {
730            ComputeError(out) if &*out == "}" => {},
731            e => panic!("{e}"),
732        }
733    }
734}