Skip to main content

cairo_lang_lowering/
diagnostic.rs

1use cairo_lang_defs::diagnostic_utils::StableLocation;
2use cairo_lang_diagnostics::{
3    DiagnosticAdded, DiagnosticEntry, DiagnosticNote, DiagnosticsBuilder, Severity, error_code,
4};
5use cairo_lang_filesystem::ids::SpanInFile;
6use cairo_lang_semantic as semantic;
7use cairo_lang_semantic::corelib::LiteralError;
8use cairo_lang_semantic::expr::inference::InferenceError;
9use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
10use salsa::Database;
11
12use crate::Location;
13
14pub type LoweringDiagnostics<'db> = DiagnosticsBuilder<'db, LoweringDiagnostic<'db>>;
15pub trait LoweringDiagnosticsBuilder<'db> {
16    fn report(
17        &mut self,
18        stable_ptr: impl Into<SyntaxStablePtrId<'db>>,
19        kind: LoweringDiagnosticKind<'db>,
20    ) -> DiagnosticAdded {
21        self.report_by_location(Location::new(StableLocation::new(stable_ptr.into())), kind)
22    }
23    fn report_by_location(
24        &mut self,
25        location: Location<'db>,
26        kind: LoweringDiagnosticKind<'db>,
27    ) -> DiagnosticAdded;
28}
29impl<'db> LoweringDiagnosticsBuilder<'db> for LoweringDiagnostics<'db> {
30    fn report_by_location(
31        &mut self,
32        location: Location<'db>,
33        kind: LoweringDiagnosticKind<'db>,
34    ) -> DiagnosticAdded {
35        self.add(LoweringDiagnostic { location, kind })
36    }
37}
38
39#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
40pub struct LoweringDiagnostic<'db> {
41    pub location: Location<'db>,
42    pub kind: LoweringDiagnosticKind<'db>,
43}
44
45impl<'db> DiagnosticEntry<'db> for LoweringDiagnostic<'db> {
46    fn format(&self, db: &'db dyn Database) -> String {
47        match &self.kind {
48            LoweringDiagnosticKind::Unreachable { .. } => "Unreachable code".into(),
49            LoweringDiagnosticKind::VariableMoved { .. } => "Variable was previously moved.".into(),
50            LoweringDiagnosticKind::VariableNotDropped { .. } => "Variable not dropped.".into(),
51            LoweringDiagnosticKind::DesnappingANonCopyableType { .. } => {
52                "Cannot desnap a non copyable type.".into()
53            }
54            LoweringDiagnosticKind::MatchError(match_err) => match_err.format(),
55            LoweringDiagnosticKind::CannotInlineFunctionThatMightCallItself => {
56                "Cannot inline a function that might call itself.".into()
57            }
58            LoweringDiagnosticKind::MemberPathLoop => {
59                "Currently, loops must change the entire variable.".into()
60            }
61            LoweringDiagnosticKind::UnexpectedError => {
62                "Unexpected error has occurred, Please submit a full bug report. \
63                See https://github.com/starkware-libs/cairo/issues/new/choose for instructions.\
64                "
65                .into()
66            }
67            LoweringDiagnosticKind::NoPanicFunctionCycle => {
68                "Call cycle of `nopanic` functions is not allowed.".into()
69            }
70            LoweringDiagnosticKind::LiteralError(literal_error) => literal_error.format(db),
71            LoweringDiagnosticKind::UnsupportedPattern => {
72                "Inner patterns are not allowed in this context.".into()
73            }
74            LoweringDiagnosticKind::Unsupported => "Unsupported feature.".into(),
75            LoweringDiagnosticKind::FixedSizeArrayNonCopyableType => {
76                "Fixed size array inner type must implement the `Copy` trait when the array size \
77                 is greater than 1."
78                    .into()
79            }
80            LoweringDiagnosticKind::EmptyRepeatedElementFixedSizeArray => {
81                "Fixed size array repeated element size must be greater than 0.".into()
82            }
83        }
84    }
85
86    fn severity(&self) -> Severity {
87        match self.kind {
88            LoweringDiagnosticKind::Unreachable { .. }
89            | LoweringDiagnosticKind::MatchError(MatchError {
90                kind: _,
91                error: MatchDiagnostic::UnreachableMatchArm,
92            }) => Severity::Warning,
93            _ => Severity::Error,
94        }
95    }
96
97    fn notes(&self, _db: &dyn Database) -> &[DiagnosticNote<'_>] {
98        &self.location.notes
99    }
100
101    fn location(&self, db: &'db dyn Database) -> SpanInFile<'db> {
102        if let LoweringDiagnosticKind::Unreachable { block_end_ptr } = &self.kind {
103            return self.location.stable_location.span_in_file_until(db, *block_end_ptr);
104        }
105        self.location.stable_location.span_in_file(db)
106    }
107
108    fn error_code(&self) -> Option<cairo_lang_diagnostics::ErrorCode> {
109        Some(match &self.kind {
110            LoweringDiagnosticKind::Unreachable { .. } => error_code!(E3000),
111            LoweringDiagnosticKind::VariableMoved { .. } => error_code!(E3001),
112            LoweringDiagnosticKind::VariableNotDropped { .. } => error_code!(E3002),
113            LoweringDiagnosticKind::DesnappingANonCopyableType { .. } => error_code!(E3003),
114            LoweringDiagnosticKind::MatchError(_) => error_code!(E3004),
115            LoweringDiagnosticKind::CannotInlineFunctionThatMightCallItself => error_code!(E3005),
116            LoweringDiagnosticKind::MemberPathLoop => error_code!(E3006),
117            LoweringDiagnosticKind::UnexpectedError => error_code!(E3007),
118            LoweringDiagnosticKind::NoPanicFunctionCycle => error_code!(E3008),
119            LoweringDiagnosticKind::LiteralError(_) => error_code!(E3009),
120            LoweringDiagnosticKind::UnsupportedPattern => error_code!(E3010),
121            LoweringDiagnosticKind::Unsupported => error_code!(E3011),
122            LoweringDiagnosticKind::FixedSizeArrayNonCopyableType => error_code!(E3012),
123            LoweringDiagnosticKind::EmptyRepeatedElementFixedSizeArray => error_code!(E3013),
124        })
125    }
126
127    fn is_same_kind(&self, other: &Self) -> bool {
128        other.kind == self.kind
129    }
130}
131
132impl<'db> MatchError<'db> {
133    fn format(&self) -> String {
134        match (&self.error, &self.kind) {
135            (MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::Match) => {
136                format!("Unsupported matched type. Type: `{matched_type}`.")
137            }
138            (MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::IfLet) => {
139                format!("Unsupported type in if-let. Type: `{matched_type}`.")
140            }
141            (MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::WhileLet(_, _)) => {
142                format!("Unsupported type in while-let. Type: `{matched_type}`.")
143            }
144            (MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::Match) => {
145                "Unsupported matched value. Currently, match on tuples only supports enums as \
146                 tuple members."
147                    .into()
148            }
149            (MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::IfLet) => {
150                "Unsupported value in if-let. Currently, if-let on tuples only supports enums as \
151                 tuple members."
152                    .into()
153            }
154            (MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::WhileLet(_, _)) => {
155                "Unsupported value in while-let. Currently, while-let on tuples only supports \
156                 enums as tuple members."
157                    .into()
158            }
159            (MatchDiagnostic::UnsupportedMatchArmNotAVariant, _) => {
160                "Unsupported pattern - not a variant.".into()
161            }
162            (MatchDiagnostic::UnsupportedMatchArmNotATuple, _) => {
163                "Unsupported pattern - not a tuple.".into()
164            }
165            (MatchDiagnostic::UnsupportedMatchArmNotALiteral, MatchKind::Match) => {
166                "Unsupported match arm - not a literal.".into()
167            }
168            (MatchDiagnostic::UnsupportedMatchArmNonSequential, MatchKind::Match) => {
169                "Unsupported match - numbers must be sequential starting from 0.".into()
170            }
171            (
172                MatchDiagnostic::UnsupportedMatchArmNotALiteral
173                | MatchDiagnostic::UnsupportedMatchArmNonSequential,
174                MatchKind::IfLet | MatchKind::WhileLet(_, _),
175            ) => unreachable!("Numeric values are not supported in if/while-let conditions."),
176            (MatchDiagnostic::NonExhaustiveMatch(variant), MatchKind::Match) => {
177                format!("Match is non-exhaustive: `{variant}` not covered.")
178            }
179            (MatchDiagnostic::NonExhaustiveMatch(_), MatchKind::IfLet) => {
180                unreachable!("If-let is not required to be exhaustive.")
181            }
182            (MatchDiagnostic::NonExhaustiveMatch(_), MatchKind::WhileLet(_, _)) => {
183                unreachable!("While-let is not required to be exhaustive.")
184            }
185            (MatchDiagnostic::UnreachableMatchArm, MatchKind::Match) => {
186                "Unreachable pattern arm.".into()
187            }
188            (MatchDiagnostic::UnreachableMatchArm, MatchKind::IfLet) => {
189                "Unreachable clause.".into()
190            }
191            (MatchDiagnostic::UnreachableMatchArm, MatchKind::WhileLet(_, _)) => {
192                unreachable!("While-let does not have two arms.")
193            }
194            (MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::Match) => {
195                unreachable!("Numeric values are supported in match conditions.")
196            }
197            (MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::IfLet) => {
198                "Numeric values are not supported in if-let conditions.".into()
199            }
200            (MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::WhileLet(_, _)) => {
201                "Numeric values are not supported in while-let conditions.".into()
202            }
203        }
204    }
205}
206
207#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
208pub enum LoweringDiagnosticKind<'db> {
209    Unreachable { block_end_ptr: SyntaxStablePtrId<'db> },
210    VariableMoved { inference_error: InferenceError<'db> },
211    VariableNotDropped { drop_err: InferenceError<'db>, destruct_err: InferenceError<'db> },
212    MatchError(MatchError<'db>),
213    DesnappingANonCopyableType { inference_error: InferenceError<'db> },
214    UnexpectedError,
215    CannotInlineFunctionThatMightCallItself,
216    MemberPathLoop,
217    NoPanicFunctionCycle,
218    LiteralError(LiteralError<'db>),
219    FixedSizeArrayNonCopyableType,
220    EmptyRepeatedElementFixedSizeArray,
221    UnsupportedPattern,
222    Unsupported,
223}
224
225/// Error in a match-like construct.
226/// contains which construct the error occurred in and the error itself.
227#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
228pub struct MatchError<'db> {
229    pub kind: MatchKind<'db>,
230    pub error: MatchDiagnostic,
231}
232
233/// The type of branch construct the error occurred in.
234#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
235pub enum MatchKind<'db> {
236    Match,
237    IfLet,
238    WhileLet(semantic::ExprId, SyntaxStablePtrId<'db>),
239}
240
241unsafe impl<'db> salsa::Update for MatchKind<'db> {
242    unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
243        let old_value = unsafe { &mut *old_pointer };
244        match (old_value, &new_value) {
245            (MatchKind::Match, MatchKind::Match) | (MatchKind::IfLet, MatchKind::IfLet) => false,
246            (MatchKind::WhileLet(expr, end_ptr), MatchKind::WhileLet(new_expr, new_end_ptr)) => {
247                if unsafe { SyntaxStablePtrId::maybe_update(end_ptr, *new_end_ptr) } {
248                    *expr = *new_expr;
249                    true
250                } else if expr != new_expr {
251                    *end_ptr = *new_end_ptr;
252                    *expr = *new_expr;
253                    true
254                } else {
255                    false
256                }
257            }
258            (old_value, new_value) => {
259                *old_value = *new_value;
260                true
261            }
262        }
263    }
264}
265
266#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
267pub enum MatchDiagnostic {
268    /// TODO(TomerStarkware): Get rid of the string and pass the type information directly.
269    UnsupportedMatchedType(String),
270    UnsupportedMatchedValueTuple,
271    UnsupportedMatchArmNotAVariant,
272    UnsupportedMatchArmNotATuple,
273
274    UnreachableMatchArm,
275    NonExhaustiveMatch(String),
276
277    UnsupportedMatchArmNotALiteral,
278    UnsupportedMatchArmNonSequential,
279    UnsupportedNumericInLetCondition,
280}