Skip to main content

match_lang/
error.rs

1//! Why a check could not be completed.
2
3use core::fmt;
4
5/// Why a check could not produce a [`Report`](crate::Report).
6///
7/// # Examples
8///
9/// ```
10/// use match_lang::{check, Arm, Error, Model, Pat, Reason, Signature};
11///
12/// struct Bytes;
13///
14/// impl Model for Bytes {
15///     type Ty = ();
16///     fn signature(&self, _: &()) -> Signature<()> {
17///         Signature::Int { min: 0, max: 255 }
18///     }
19///     fn fields(&self, _: &(), _: u32, _: &mut Vec<()>) {}
20/// }
21///
22/// let err = check(&Bytes, &(), &[Arm::new(Pat::int(300))]).unwrap_err();
23/// assert_eq!(err, Error::InvalidPattern { arm: 0, node: 0, reason: Reason::OutOfDomain });
24/// ```
25#[derive(Clone, Debug, PartialEq, Eq)]
26#[non_exhaustive]
27pub enum Error {
28    /// The analysis took more steps than the
29    /// [step limit](crate::Checker::with_step_limit) allows. Deciding
30    /// exhaustiveness is NP-hard in general, so some matrices (typically
31    /// many columns of overlapping or-patterns or ranges) need exponential
32    /// work; the limit turns them into this error instead of a hang. Raise
33    /// the limit if the match is legitimate, or report it to the user as too
34    /// complex to check.
35    StepLimit {
36        /// The limit that was exceeded.
37        limit: u64,
38    },
39    /// A pattern does not fit the type it is matched against. This is a type
40    /// error the language's checker should have caught; nothing was analysed.
41    InvalidPattern {
42        /// The index of the arm.
43        arm: usize,
44        /// The preorder index of the offending node within the arm's pattern
45        /// (see [`Pat`](crate::Pat#node-numbering)).
46        node: usize,
47        /// What is wrong with it.
48        reason: Reason,
49    },
50    /// The model answered inconsistently: the same type got different
51    /// signatures or field counts during one check, or a signature was
52    /// itself invalid (an `Int` with `min > max`). Fix the model.
53    Model {
54        /// What was inconsistent.
55        reason: &'static str,
56    },
57    /// The patterns, or the matrix built from them, need more than
58    /// `u32::MAX - 1` nodes, rows, or cells.
59    TooLarge,
60}
61
62/// What is wrong with a pattern, in [`Error::InvalidPattern`].
63///
64/// # Examples
65///
66/// ```
67/// use match_lang::Reason;
68///
69/// let reason = Reason::Fields { expected: 1, found: 2 };
70/// assert_eq!(reason.to_string(), "expected 1 fields, found 2");
71/// ```
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
73#[non_exhaustive]
74pub enum Reason {
75    /// The kind of pattern does not fit the type's signature, such as an
76    /// integer pattern on a sum type.
77    Kind,
78    /// A variant index at or past the sum type's variant count.
79    Variant {
80        /// The variant index used.
81        index: u32,
82        /// The number of variants the type has.
83        count: u32,
84    },
85    /// A variant or product pattern with the wrong number of fields.
86    Fields {
87        /// The number of fields the model reports.
88        expected: usize,
89        /// The number of field patterns given.
90        found: usize,
91    },
92    /// A range whose low end is above its high end.
93    EmptyRange,
94    /// An integer or range outside the bounds of an `Int` type.
95    OutOfDomain,
96    /// An array pattern that cannot have the array's length: a fixed slice
97    /// pattern of another length, or a rest pattern needing more elements.
98    Length {
99        /// The array's length.
100        expected: u32,
101        /// The number of elements the pattern requires.
102        found: usize,
103    },
104}
105
106impl fmt::Display for Error {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            Error::StepLimit { limit } => {
110                write!(
111                    f,
112                    "match too complex to check: exceeded the step limit of {limit}"
113                )
114            }
115            Error::InvalidPattern { arm, node, reason } => {
116                write!(f, "arm {arm}, pattern node {node}: {reason}")
117            }
118            Error::Model { reason } => write!(f, "inconsistent constructor model: {reason}"),
119            Error::TooLarge => f.write_str("match too large: more than u32::MAX - 1 nodes"),
120        }
121    }
122}
123
124impl fmt::Display for Reason {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            Reason::Kind => f.write_str("pattern does not fit the type"),
128            Reason::Variant { index, count } => {
129                write!(f, "variant {index} does not exist (the type has {count})")
130            }
131            Reason::Fields { expected, found } => {
132                write!(f, "expected {expected} fields, found {found}")
133            }
134            Reason::EmptyRange => f.write_str("range is empty (low end above high end)"),
135            Reason::OutOfDomain => f.write_str("value outside the type's bounds"),
136            Reason::Length { expected, found } => {
137                write!(
138                    f,
139                    "array of length {expected} cannot match a pattern of {found} elements"
140                )
141            }
142        }
143    }
144}
145
146impl core::error::Error for Error {}
147
148#[cfg(test)]
149mod tests {
150    use alloc::string::ToString;
151
152    use super::*;
153
154    #[test]
155    fn test_error_messages_name_the_problem() {
156        assert!(
157            Error::StepLimit { limit: 5 }
158                .to_string()
159                .contains("step limit of 5")
160        );
161        assert_eq!(
162            Error::InvalidPattern {
163                arm: 1,
164                node: 2,
165                reason: Reason::Kind
166            }
167            .to_string(),
168            "arm 1, pattern node 2: pattern does not fit the type"
169        );
170        assert!(Error::Model { reason: "x" }.to_string().ends_with(": x"));
171        assert!(Error::TooLarge.to_string().contains("too large"));
172        assert!(
173            Reason::Variant { index: 3, count: 2 }
174                .to_string()
175                .contains("variant 3")
176        );
177        assert!(Reason::EmptyRange.to_string().contains("empty"));
178        assert!(Reason::OutOfDomain.to_string().contains("bounds"));
179        assert!(
180            Reason::Length {
181                expected: 2,
182                found: 3
183            }
184            .to_string()
185            .contains("length 2")
186        );
187    }
188}