1#[cfg(test)]
2#[path = "test.rs"]
3mod test;
4
5use cairo_lang_defs::ids::TraitFunctionId;
6use cairo_lang_diagnostics::{DiagnosticNote, Diagnostics};
7use cairo_lang_semantic::corelib::CorelibSemantic;
8use cairo_lang_semantic::items::functions::{GenericFunctionId, ImplGenericFunctionId};
9use cairo_lang_utils::Intern;
10use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
11use itertools::{Itertools, zip_eq};
12use salsa::Database;
13
14pub use self::demand::Demand;
15use self::demand::{AuxCombine, DemandReporter};
16use crate::analysis::{Analyzer, BackAnalysis, StatementLocation};
17use crate::diagnostic::LoweringDiagnosticKind::*;
18use crate::diagnostic::{LoweringDiagnostic, LoweringDiagnostics, LoweringDiagnosticsBuilder};
19use crate::ids::{FunctionId, LocationId, SemanticFunctionIdEx};
20use crate::{BlockId, Lowered, MatchInfo, Statement, VarRemapping, VarUsage, VariableId};
21
22pub mod demand;
23
24pub type BorrowCheckerDemand<'db> = Demand<VariableId, LocationId<'db>, PanicState>;
25pub struct BorrowChecker<'db, 'mt, 'r> {
26 db: &'db dyn Database,
27 diagnostics: &'mt mut LoweringDiagnostics<'db>,
28 lowered: &'r Lowered<'db>,
29 potential_destruct_calls: PotentialDestructCalls<'db>,
30 destruct_fn: TraitFunctionId<'db>,
31 panic_destruct_fn: TraitFunctionId<'db>,
32 is_panic_destruct_fn: bool,
33}
34
35#[derive(Copy, Clone, Default)]
38pub enum PanicState {
39 EndsWithPanic,
40 #[default]
41 Otherwise,
42}
43impl AuxCombine for PanicState {
44 fn merge<'a, I: Iterator<Item = &'a Self>>(mut iter: I) -> Self
45 where
46 Self: 'a,
47 {
48 if iter.all(|x| matches!(x, Self::EndsWithPanic)) {
49 Self::EndsWithPanic
50 } else {
51 Self::Otherwise
52 }
53 }
54}
55
56#[derive(Copy, Clone, Debug)]
58pub enum DropPosition<'db> {
59 Panic(LocationId<'db>),
61 Diverge(LocationId<'db>),
63}
64impl<'db> DropPosition<'db> {
65 fn enrich_as_notes(self, db: &'db dyn Database, notes: &mut Vec<DiagnosticNote<'db>>) {
66 let (text, location) = match self {
67 Self::Panic(location) => {
68 ("the variable needs to be dropped due to the potential panic here", location)
69 }
70 Self::Diverge(location) => {
71 ("the variable needs to be dropped due to the divergence here", location)
72 }
73 };
74 let location = location.long(db);
75 notes.push(DiagnosticNote::with_location(
76 text.into(),
77 location.stable_location.span_in_file(db),
78 ));
79 notes.extend(location.notes.iter().cloned());
80 }
81}
82
83impl<'db, 'mt> DemandReporter<VariableId, PanicState> for BorrowChecker<'db, 'mt, '_> {
84 type IntroducePosition = (Option<DropPosition<'db>>, BlockId);
87 type UsePosition = LocationId<'db>;
88
89 fn drop_aux(
90 &mut self,
91 (opt_drop_position, block_id): (Option<DropPosition<'db>>, BlockId),
92 var_id: VariableId,
93 panic_state: PanicState,
94 ) {
95 let var = &self.lowered.variables[var_id];
96 let Err(drop_err) = var.info.droppable.clone() else {
97 return;
98 };
99 let db = self.db;
100 let mut add_called_fn = |impl_id, function| {
101 self.potential_destruct_calls.entry(block_id).or_default().push(
102 cairo_lang_semantic::FunctionLongId {
103 function: cairo_lang_semantic::ConcreteFunction {
104 generic_function: GenericFunctionId::Impl(ImplGenericFunctionId {
105 impl_id,
106 function,
107 }),
108 generic_args: vec![],
109 },
110 }
111 .intern(db)
112 .lowered(db),
113 );
114 };
115 let destruct_err = match var.info.destruct_impl.clone() {
116 Ok(impl_id) => {
117 add_called_fn(impl_id, self.destruct_fn);
118 return;
119 }
120 Err(err) => err,
121 };
122 let panic_destruct_err = if matches!(panic_state, PanicState::EndsWithPanic) {
123 match var.info.panic_destruct_impl.clone() {
124 Ok(impl_id) => {
125 add_called_fn(impl_id, self.panic_destruct_fn);
126 return;
127 }
128 Err(err) => Some(err),
129 }
130 } else {
131 None
132 };
133
134 let mut location = var.location.long(db).clone();
135 if let Some(drop_position) = opt_drop_position {
136 drop_position.enrich_as_notes(db, &mut location.notes);
137 }
138 self.diagnostics.report_by_location(
139 location
140 .with_note(DiagnosticNote::text_only(drop_err.format(db)))
141 .with_note(DiagnosticNote::text_only(destruct_err.format(db)))
142 .maybe_with_note(
143 panic_destruct_err.map(|err| DiagnosticNote::text_only(err.format(db))),
144 ),
145 VariableNotDropped { drop_err, destruct_err },
146 );
147 }
148
149 fn dup(
150 &mut self,
151 position: LocationId<'db>,
152 var_id: VariableId,
153 next_usage_position: LocationId<'db>,
154 ) {
155 let var = &self.lowered.variables[var_id];
156 if let Err(inference_error) = var.info.copyable.clone() {
157 self.diagnostics.report_by_location(
158 next_usage_position
159 .long(self.db)
160 .clone()
161 .add_note_with_location(self.db, "variable was previously used here", position)
162 .with_note(DiagnosticNote::text_only(inference_error.format(self.db))),
163 VariableMoved { inference_error },
164 );
165 }
166 }
167}
168
169impl<'db, 'mt> Analyzer<'db, '_> for BorrowChecker<'db, 'mt, '_> {
170 type Info = BorrowCheckerDemand<'db>;
171
172 fn visit_stmt(
173 &mut self,
174 info: &mut Self::Info,
175 (block_id, _): StatementLocation,
176 stmt: &Statement<'db>,
177 ) {
178 info.variables_introduced(self, stmt.outputs(), (None, block_id));
179 match stmt {
180 Statement::Call(stmt) => {
181 if let Ok(signature) = stmt.function.signature(self.db)
182 && signature.panicable
183 {
184 let panic_demand = BorrowCheckerDemand {
186 aux: PanicState::EndsWithPanic,
187 ..Default::default()
188 };
189 let location = (Some(DropPosition::Panic(stmt.location)), block_id);
190 *info = BorrowCheckerDemand::merge_demands(
191 &[(panic_demand, location), (info.clone(), location)],
192 self,
193 );
194 }
195 }
196 Statement::Desnap(stmt) => {
197 let var = &self.lowered.variables[stmt.output];
198 if let Err(inference_error) = var.info.copyable.clone() {
199 self.diagnostics.report_by_location(
200 var.location
201 .long(self.db)
202 .clone()
203 .with_note(DiagnosticNote::text_only(inference_error.format(self.db))),
204 DesnappingANonCopyableType { inference_error },
205 );
206 }
207 }
208 _ => {}
209 }
210 info.variables_used(
211 self,
212 stmt.inputs().iter().map(|VarUsage { var_id, location }| (var_id, *location)),
213 );
214 }
215
216 fn visit_goto(
217 &mut self,
218 info: &mut Self::Info,
219 _statement_location: StatementLocation,
220 _target_block_id: BlockId,
221 remapping: &VarRemapping<'db>,
222 ) {
223 info.apply_remapping(
224 self,
225 remapping
226 .iter()
227 .map(|(dst, VarUsage { var_id: src, location })| (dst, (src, *location))),
228 );
229 }
230
231 fn merge_match(
232 &mut self,
233 (block_id, _): StatementLocation,
234 match_info: &MatchInfo<'db>,
235 infos: impl Iterator<Item = Self::Info>,
236 ) -> Self::Info {
237 let infos: Vec<_> = infos.collect();
238 let arm_demands = zip_eq(match_info.arms(), &infos)
239 .map(|(arm, demand)| {
240 let mut demand = demand.clone();
241 demand.variables_introduced(self, &arm.var_ids, (None, block_id));
242 (demand, (Some(DropPosition::Diverge(*match_info.location())), block_id))
243 })
244 .collect_vec();
245 let mut demand = BorrowCheckerDemand::merge_demands(&arm_demands, self);
246 demand.variables_used(
247 self,
248 match_info.inputs().iter().map(|VarUsage { var_id, location }| (var_id, *location)),
249 );
250 demand
251 }
252
253 fn info_from_return(
254 &mut self,
255 _statement_location: StatementLocation,
256 vars: &[VarUsage<'db>],
257 ) -> Self::Info {
258 let mut info = if self.is_panic_destruct_fn {
259 BorrowCheckerDemand { aux: PanicState::EndsWithPanic, ..Default::default() }
260 } else {
261 BorrowCheckerDemand::default()
262 };
263
264 info.variables_used(
265 self,
266 vars.iter().map(|VarUsage { var_id, location }| (var_id, *location)),
267 );
268 info
269 }
270
271 fn info_from_panic(
272 &mut self,
273 _statement_location: StatementLocation,
274 data: &VarUsage<'db>,
275 ) -> Self::Info {
276 let mut info = BorrowCheckerDemand { aux: PanicState::EndsWithPanic, ..Default::default() };
277 info.variables_used(self, std::iter::once((&data.var_id, data.location)));
278 info
279 }
280}
281
282pub type PotentialDestructCalls<'db> = UnorderedHashMap<BlockId, Vec<FunctionId<'db>>>;
284
285#[derive(Eq, PartialEq, Debug, Default, salsa::Update)]
287pub struct BorrowCheckResult<'db> {
288 pub block_extra_calls: PotentialDestructCalls<'db>,
290 pub diagnostics: Diagnostics<'db, LoweringDiagnostic<'db>>,
292}
293
294pub fn borrow_check<'db>(
297 db: &'db dyn Database,
298 is_panic_destruct_fn: bool,
299 lowered: &'db Lowered<'db>,
300) -> BorrowCheckResult<'db> {
301 if lowered.blocks.has_root().is_err() {
302 return Default::default();
303 }
304 let mut diagnostics = LoweringDiagnostics::default();
305 let info = db.core_info();
306 let destruct_fn = info.destruct_fn;
307 let panic_destruct_fn = info.panic_destruct_fn;
308
309 let checker = BorrowChecker {
310 db,
311 diagnostics: &mut diagnostics,
312 lowered,
313 potential_destruct_calls: Default::default(),
314 destruct_fn,
315 panic_destruct_fn,
316 is_panic_destruct_fn,
317 };
318 let mut analysis = BackAnalysis::new(lowered, checker);
319 let mut root_demand = analysis.get_root_info();
320 root_demand.variables_introduced(
321 &mut analysis.analyzer,
322 &lowered.parameters,
323 (None, BlockId::root()),
324 );
325 let block_extra_calls = analysis.analyzer.potential_destruct_calls;
326
327 let finalize_res = root_demand.finalize();
328 if !lowered.diagnostics.has_errors() {
331 assert!(finalize_res, "Undefined variable should not happen at this stage");
332 }
333
334 BorrowCheckResult { block_extra_calls, diagnostics: diagnostics.build() }
335}
336
337pub fn borrow_check_possible_withdraw_gas<'db>(
340 db: &'db dyn Database,
341 location_id: LocationId<'db>,
342 lowered: &Lowered<'db>,
343 diagnostics: &mut LoweringDiagnostics<'db>,
344) {
345 let info = db.core_info();
346 let destruct_fn = info.destruct_fn;
347 let panic_destruct_fn = info.panic_destruct_fn;
348 let mut checker = BorrowChecker {
349 db,
350 diagnostics,
351 lowered,
352 potential_destruct_calls: Default::default(),
353 destruct_fn,
354 panic_destruct_fn,
355 is_panic_destruct_fn: false,
356 };
357 let position = (
358 Some(DropPosition::Panic(location_id.with_auto_generation_note(db, "withdraw_gas"))),
359 BlockId::root(),
360 );
361 for param in &lowered.parameters {
362 checker.drop_aux(position, *param, PanicState::EndsWithPanic);
363 }
364}