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