Skip to main content

cairo_lang_lowering/borrow_check/
mod.rs

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