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#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
209pub struct MatchError<'db> {
210 pub kind: MatchKind<'db>,
211 pub error: MatchDiagnostic,
212}
213
214#[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 UnsupportedMatchedType(String),
251 UnsupportedMatchedValueTuple,
252 UnsupportedMatchArmNotAVariant,
253 UnsupportedMatchArmNotATuple,
254
255 UnreachableMatchArm,
256 NonExhaustiveMatch(String),
257
258 UnsupportedMatchArmNotALiteral,
259 UnsupportedMatchArmNonSequential,
260 UnsupportedNumericInLetCondition,
261}