cairo_lang_lowering/
diagnostic.rs

1use cairo_lang_defs::diagnostic_utils::StableLocation;
2use cairo_lang_diagnostics::{
3    DiagnosticAdded, DiagnosticEntry, DiagnosticNote, DiagnosticsBuilder, Severity,
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 is_same_kind(&self, other: &Self) -> bool {
109        other.kind == self.kind
110    }
111}
112
113impl<'db> MatchError<'db> {
114    fn format(&self) -> String {
115        match (&self.error, &self.kind) {
116            (MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::Match) => {
117                format!("Unsupported matched type. Type: `{matched_type}`.")
118            }
119            (MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::IfLet) => {
120                format!("Unsupported type in if-let. Type: `{matched_type}`.")
121            }
122            (MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::WhileLet(_, _)) => {
123                format!("Unsupported type in while-let. Type: `{matched_type}`.")
124            }
125            (MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::Match) => {
126                "Unsupported matched value. Currently, match on tuples only supports enums as \
127                 tuple members."
128                    .into()
129            }
130            (MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::IfLet) => {
131                "Unsupported value in if-let. Currently, if-let on tuples only supports enums as \
132                 tuple members."
133                    .into()
134            }
135            (MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::WhileLet(_, _)) => {
136                "Unsupported value in while-let. Currently, while-let on tuples only supports \
137                 enums as tuple members."
138                    .into()
139            }
140            (MatchDiagnostic::UnsupportedMatchArmNotAVariant, _) => {
141                "Unsupported pattern - not a variant.".into()
142            }
143            (MatchDiagnostic::UnsupportedMatchArmNotATuple, _) => {
144                "Unsupported pattern - not a tuple.".into()
145            }
146            (MatchDiagnostic::UnsupportedMatchArmNotALiteral, MatchKind::Match) => {
147                "Unsupported match arm - not a literal.".into()
148            }
149            (MatchDiagnostic::UnsupportedMatchArmNonSequential, MatchKind::Match) => {
150                "Unsupported match - numbers must be sequential starting from 0.".into()
151            }
152            (
153                MatchDiagnostic::UnsupportedMatchArmNotALiteral
154                | MatchDiagnostic::UnsupportedMatchArmNonSequential,
155                MatchKind::IfLet | MatchKind::WhileLet(_, _),
156            ) => unreachable!("Numeric values are not supported in if/while-let conditions."),
157            (MatchDiagnostic::NonExhaustiveMatch(variant), MatchKind::Match) => {
158                format!("Match is non-exhaustive: `{variant}` not covered.")
159            }
160            (MatchDiagnostic::NonExhaustiveMatch(_), MatchKind::IfLet) => {
161                unreachable!("If-let is not required to be exhaustive.")
162            }
163            (MatchDiagnostic::NonExhaustiveMatch(_), MatchKind::WhileLet(_, _)) => {
164                unreachable!("While-let is not required to be exhaustive.")
165            }
166            (MatchDiagnostic::UnreachableMatchArm, MatchKind::Match) => {
167                "Unreachable pattern arm.".into()
168            }
169            (MatchDiagnostic::UnreachableMatchArm, MatchKind::IfLet) => {
170                "Unreachable clause.".into()
171            }
172            (MatchDiagnostic::UnreachableMatchArm, MatchKind::WhileLet(_, _)) => {
173                unreachable!("While-let does not have two arms.")
174            }
175            (MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::Match) => {
176                unreachable!("Numeric values are supported in match conditions.")
177            }
178            (MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::IfLet) => {
179                "Numeric values are not supported in if-let conditions.".into()
180            }
181            (MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::WhileLet(_, _)) => {
182                "Numeric values are not supported in while-let conditions.".into()
183            }
184        }
185    }
186}
187
188#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
189pub enum LoweringDiagnosticKind<'db> {
190    Unreachable { block_end_ptr: SyntaxStablePtrId<'db> },
191    VariableMoved { inference_error: InferenceError<'db> },
192    VariableNotDropped { drop_err: InferenceError<'db>, destruct_err: InferenceError<'db> },
193    MatchError(MatchError<'db>),
194    DesnappingANonCopyableType { inference_error: InferenceError<'db> },
195    UnexpectedError,
196    CannotInlineFunctionThatMightCallItself,
197    MemberPathLoop,
198    NoPanicFunctionCycle,
199    LiteralError(LiteralError<'db>),
200    FixedSizeArrayNonCopyableType,
201    EmptyRepeatedElementFixedSizeArray,
202    UnsupportedPattern,
203    Unsupported,
204}
205
206/// Error in a match-like construct.
207/// contains which construct the error occurred in and the error itself.
208#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
209pub struct MatchError<'db> {
210    pub kind: MatchKind<'db>,
211    pub error: MatchDiagnostic,
212}
213
214/// The type of branch construct the error occurred in.
215#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
216pub enum MatchKind<'db> {
217    Match,
218    IfLet,
219    WhileLet(semantic::ExprId, SyntaxStablePtrId<'db>),
220}
221
222unsafe impl<'db> salsa::Update for MatchKind<'db> {
223    unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
224        let old_value = unsafe { &mut *old_pointer };
225        match (old_value, &new_value) {
226            (MatchKind::Match, MatchKind::Match) | (MatchKind::IfLet, MatchKind::IfLet) => false,
227            (MatchKind::WhileLet(expr, end_ptr), MatchKind::WhileLet(new_expr, new_end_ptr)) => {
228                if unsafe { SyntaxStablePtrId::maybe_update(end_ptr, *new_end_ptr) } {
229                    *expr = *new_expr;
230                    true
231                } else if expr != new_expr {
232                    *end_ptr = *new_end_ptr;
233                    *expr = *new_expr;
234                    true
235                } else {
236                    false
237                }
238            }
239            (old_value, new_value) => {
240                *old_value = *new_value;
241                true
242            }
243        }
244    }
245}
246
247#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
248pub enum MatchDiagnostic {
249    /// TODO(TomerStarkware): Get rid of the string and pass the type information directly.
250    UnsupportedMatchedType(String),
251    UnsupportedMatchedValueTuple,
252    UnsupportedMatchArmNotAVariant,
253    UnsupportedMatchArmNotATuple,
254
255    UnreachableMatchArm,
256    NonExhaustiveMatch(String),
257
258    UnsupportedMatchArmNotALiteral,
259    UnsupportedMatchArmNonSequential,
260    UnsupportedNumericInLetCondition,
261}