Skip to main content

cairo_lang_lowering/
diagnostic.rs

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