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