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 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 { .. } => Severity::Warning,
91            _ => Severity::Error,
92        }
93    }
94
95    fn notes(&self, _db: &Self::DbType) -> &[DiagnosticNote] {
96        &self.location.notes
97    }
98
99    fn location(&self, db: &Self::DbType) -> DiagnosticLocation {
100        if let LoweringDiagnosticKind::Unreachable { last_statement_ptr } = &self.kind {
101            return self
102                .location
103                .stable_location
104                .diagnostic_location_until(db.upcast(), *last_statement_ptr);
105        }
106        self.location.stable_location.diagnostic_location(db.upcast())
107    }
108
109    fn is_same_kind(&self, other: &Self) -> bool {
110        other.kind == self.kind
111    }
112}
113
114impl MatchError {
115    fn format(&self) -> String {
116        match (&self.error, &self.kind) {
117            (MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::Match) => {
118                format!("Unsupported matched type. Type: `{}`.", matched_type)
119            }
120            (MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::IfLet) => {
121                format!("Unsupported type in if-let. Type: `{}`.", matched_type)
122            }
123            (MatchDiagnostic::UnsupportedMatchedType(matched_type), MatchKind::WhileLet(_, _)) => {
124                format!("Unsupported type in while-let. Type: `{}`.", matched_type)
125            }
126            (MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::Match) => {
127                "Unsupported matched value. Currently, match on tuples only supports enums as \
128                 tuple members."
129                    .into()
130            }
131            (MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::IfLet) => {
132                "Unsupported value in if-let. Currently, if-let on tuples only supports enums as \
133                 tuple members."
134                    .into()
135            }
136            (MatchDiagnostic::UnsupportedMatchedValueTuple, MatchKind::WhileLet(_, _)) => {
137                "Unsupported value in while-let. Currently, while-let on tuples only supports \
138                 enums as tuple members."
139                    .into()
140            }
141            (MatchDiagnostic::UnsupportedMatchArmNotAVariant, _) => {
142                "Unsupported pattern - not a variant.".into()
143            }
144            (MatchDiagnostic::UnsupportedMatchArmNotATuple, _) => {
145                "Unsupported pattern - not a tuple.".into()
146            }
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::NonExhaustiveMatchFelt252, MatchKind::Match) => {
155                "Match is non exhaustive - match over a numerical value must have a wildcard card \
156                 pattern (`_`)."
157                    .into()
158            }
159
160            (
161                MatchDiagnostic::UnsupportedMatchArmNotALiteral
162                | MatchDiagnostic::UnsupportedMatchArmNonSequential
163                | MatchDiagnostic::NonExhaustiveMatchFelt252,
164                MatchKind::IfLet | MatchKind::WhileLet(_, _),
165            ) => unreachable!("Numeric values are not supported in if/while-let conditions."),
166
167            (MatchDiagnostic::MissingMatchArm(variant), MatchKind::Match) => {
168                format!("Missing match arm: `{}` not covered.", variant)
169            }
170            (MatchDiagnostic::MissingMatchArm(_), MatchKind::IfLet) => {
171                unreachable!("If-let is not required to be exhaustive.")
172            }
173            (MatchDiagnostic::MissingMatchArm(_), MatchKind::WhileLet(_, _)) => {
174                unreachable!("While-let is not required to be exhaustive.")
175            }
176
177            (MatchDiagnostic::UnreachableMatchArm, MatchKind::Match) => {
178                "Unreachable pattern arm.".into()
179            }
180            (MatchDiagnostic::UnreachableMatchArm, MatchKind::IfLet) => {
181                "Unreachable else clause.".into()
182            }
183            (MatchDiagnostic::UnreachableMatchArm, MatchKind::WhileLet(_, _)) => {
184                unreachable!("While-let is does not have two arms.")
185            }
186            (MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::Match) => {
187                unreachable!("Numeric values are supported in match conditions.")
188            }
189            (MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::IfLet) => {
190                "Numeric values are not supported in if-let conditions.".into()
191            }
192            (MatchDiagnostic::UnsupportedNumericInLetCondition, MatchKind::WhileLet(_, _)) => {
193                "Numeric values are not supported in while-let conditions.".into()
194            }
195        }
196    }
197}
198
199#[derive(Clone, Debug, Eq, Hash, PartialEq)]
200pub enum LoweringDiagnosticKind {
201    Unreachable { last_statement_ptr: SyntaxStablePtrId },
202    VariableMoved { inference_error: InferenceError },
203    VariableNotDropped { drop_err: InferenceError, destruct_err: InferenceError },
204    MatchError(MatchError),
205    DesnappingANonCopyableType { inference_error: InferenceError },
206    UnexpectedError,
207    CannotInlineFunctionThatMightCallItself,
208    MemberPathLoop,
209    NoPanicFunctionCycle,
210    LiteralError(LiteralError),
211    FixedSizeArrayNonCopyableType,
212    EmptyRepeatedElementFixedSizeArray,
213    UnsupportedPattern,
214    Unsupported,
215}
216
217/// Error in a match-like construct.
218/// contains which construct the error occurred in and the error itself.
219#[derive(Clone, Debug, Eq, Hash, PartialEq)]
220pub struct MatchError {
221    pub kind: MatchKind,
222    pub error: MatchDiagnostic,
223}
224
225/// The type of branch construct the error occurred in.
226#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
227pub enum MatchKind {
228    Match,
229    IfLet,
230    WhileLet(semantic::ExprId, SyntaxStablePtrId),
231}
232
233#[derive(Clone, Debug, Eq, Hash, PartialEq)]
234pub enum MatchDiagnostic {
235    /// TODO(TomerStarkware): Get rid of the string and pass the type information directly.
236    UnsupportedMatchedType(String),
237    UnsupportedMatchedValueTuple,
238    UnsupportedMatchArmNotAVariant,
239    UnsupportedMatchArmNotATuple,
240
241    UnreachableMatchArm,
242    MissingMatchArm(String),
243
244    UnsupportedMatchArmNotALiteral,
245    UnsupportedMatchArmNonSequential,
246    NonExhaustiveMatchFelt252,
247    UnsupportedNumericInLetCondition,
248}