rustc_borrowck/
nll.rs

1//! The entry point of the NLL borrow checker.
2
3use std::io;
4use std::path::PathBuf;
5use std::rc::Rc;
6use std::str::FromStr;
7
8use polonius_engine::{Algorithm, AllFacts, Output};
9use rustc_data_structures::frozen::Frozen;
10use rustc_index::IndexSlice;
11use rustc_middle::mir::pretty::PrettyPrintMirOptions;
12use rustc_middle::mir::{Body, MirDumper, PassWhere, Promoted};
13use rustc_middle::ty::print::with_no_trimmed_paths;
14use rustc_middle::ty::{self, TyCtxt};
15use rustc_mir_dataflow::move_paths::MoveData;
16use rustc_mir_dataflow::points::DenseLocationMap;
17use rustc_session::config::MirIncludeSpans;
18use rustc_span::sym;
19use tracing::{debug, instrument};
20
21use crate::borrow_set::BorrowSet;
22use crate::consumers::RustcFacts;
23use crate::diagnostics::RegionErrors;
24use crate::handle_placeholders::compute_sccs_applying_placeholder_outlives_constraints;
25use crate::polonius::legacy::{
26    PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput,
27};
28use crate::polonius::{PoloniusContext, PoloniusDiagnosticsContext};
29use crate::region_infer::RegionInferenceContext;
30use crate::type_check::MirTypeckRegionConstraints;
31use crate::type_check::free_region_relations::UniversalRegionRelations;
32use crate::universal_regions::UniversalRegions;
33use crate::{
34    BorrowCheckRootCtxt, BorrowckInferCtxt, ClosureOutlivesSubject, ClosureRegionRequirements,
35    polonius, renumber,
36};
37
38/// The output of `nll::compute_regions`. This includes the computed `RegionInferenceContext`, any
39/// closure requirements to propagate, and any generated errors.
40pub(crate) struct NllOutput<'tcx> {
41    pub regioncx: RegionInferenceContext<'tcx>,
42    pub polonius_input: Option<Box<PoloniusFacts>>,
43    pub polonius_output: Option<Box<PoloniusOutput>>,
44    pub opt_closure_req: Option<ClosureRegionRequirements<'tcx>>,
45    pub nll_errors: RegionErrors<'tcx>,
46
47    /// When using `-Zpolonius=next`: the data used to compute errors and diagnostics, e.g.
48    /// localized typeck and liveness constraints.
49    pub polonius_diagnostics: Option<PoloniusDiagnosticsContext>,
50}
51
52/// Rewrites the regions in the MIR to use NLL variables, also scraping out the set of universal
53/// regions (e.g., region parameters) declared on the function. That set will need to be given to
54/// `compute_regions`.
55#[instrument(skip(infcx, body, promoted), level = "debug")]
56pub(crate) fn replace_regions_in_mir<'tcx>(
57    infcx: &BorrowckInferCtxt<'tcx>,
58    body: &mut Body<'tcx>,
59    promoted: &mut IndexSlice<Promoted, Body<'tcx>>,
60) -> UniversalRegions<'tcx> {
61    let def = body.source.def_id().expect_local();
62
63    debug!(?def);
64
65    // Compute named region information. This also renumbers the inputs/outputs.
66    let universal_regions = UniversalRegions::new(infcx, def);
67
68    // Replace all remaining regions with fresh inference variables.
69    renumber::renumber_mir(infcx, body, promoted);
70
71    if let Some(dumper) = MirDumper::new(infcx.tcx, "renumber", body) {
72        dumper.dump_mir(body);
73    }
74
75    universal_regions
76}
77
78/// Computes the closure requirements given the current inference state.
79///
80/// This is intended to be used by before [BorrowCheckRootCtxt::handle_opaque_type_uses]
81/// because applying member constraints may rely on closure requirements.
82/// This is frequently the case of async functions where pretty much everything
83/// happens inside of the inner async block but the opaque only gets constrained
84/// in the parent function.
85pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>(
86    infcx: &BorrowckInferCtxt<'tcx>,
87    body: &Body<'tcx>,
88    location_map: Rc<DenseLocationMap>,
89    universal_region_relations: &Frozen<UniversalRegionRelations<'tcx>>,
90    constraints: &MirTypeckRegionConstraints<'tcx>,
91) -> Option<ClosureRegionRequirements<'tcx>> {
92    // FIXME(#146079): we shouldn't have to clone all this stuff here.
93    // Computing the region graph should take at least some of it by reference/`Rc`.
94    let lowered_constraints = compute_sccs_applying_placeholder_outlives_constraints(
95        constraints.clone(),
96        &universal_region_relations,
97        infcx,
98    );
99    let mut regioncx = RegionInferenceContext::new(
100        &infcx,
101        lowered_constraints,
102        universal_region_relations.clone(),
103        location_map,
104    );
105
106    let (closure_region_requirements, _nll_errors) = regioncx.solve(infcx, body, None);
107    closure_region_requirements
108}
109
110/// Computes the (non-lexical) regions from the input MIR.
111///
112/// This may result in errors being reported.
113pub(crate) fn compute_regions<'tcx>(
114    root_cx: &mut BorrowCheckRootCtxt<'tcx>,
115    infcx: &BorrowckInferCtxt<'tcx>,
116    body: &Body<'tcx>,
117    location_table: &PoloniusLocationTable,
118    move_data: &MoveData<'tcx>,
119    borrow_set: &BorrowSet<'tcx>,
120    location_map: Rc<DenseLocationMap>,
121    universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
122    constraints: MirTypeckRegionConstraints<'tcx>,
123    mut polonius_facts: Option<AllFacts<RustcFacts>>,
124    polonius_context: Option<PoloniusContext>,
125) -> NllOutput<'tcx> {
126    let polonius_output = root_cx.consumer.as_ref().map_or(false, |c| c.polonius_output())
127        || infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled();
128
129    let lowered_constraints = compute_sccs_applying_placeholder_outlives_constraints(
130        constraints,
131        &universal_region_relations,
132        infcx,
133    );
134
135    // If requested, emit legacy polonius facts.
136    polonius::legacy::emit_facts(
137        &mut polonius_facts,
138        infcx.tcx,
139        location_table,
140        body,
141        borrow_set,
142        move_data,
143        &universal_region_relations,
144        &lowered_constraints,
145    );
146
147    let mut regioncx = RegionInferenceContext::new(
148        infcx,
149        lowered_constraints,
150        universal_region_relations,
151        location_map,
152    );
153
154    // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints
155    // and use them to compute loan liveness.
156    let polonius_diagnostics = polonius_context.map(|polonius_context| {
157        polonius_context.compute_loan_liveness(infcx.tcx, &mut regioncx, body, borrow_set)
158    });
159
160    // If requested: dump NLL facts, and run legacy polonius analysis.
161    let polonius_output = polonius_facts.as_ref().and_then(|polonius_facts| {
162        if infcx.tcx.sess.opts.unstable_opts.nll_facts {
163            let def_id = body.source.def_id();
164            let def_path = infcx.tcx.def_path(def_id);
165            let dir_path = PathBuf::from(&infcx.tcx.sess.opts.unstable_opts.nll_facts_dir)
166                .join(def_path.to_filename_friendly_no_crate());
167            polonius_facts.write_to_dir(dir_path, location_table).unwrap();
168        }
169
170        if polonius_output {
171            let algorithm = infcx.tcx.env_var("POLONIUS_ALGORITHM").unwrap_or("Hybrid");
172            let algorithm = Algorithm::from_str(algorithm).unwrap();
173            debug!("compute_regions: using polonius algorithm {:?}", algorithm);
174            let _prof_timer = infcx.tcx.prof.generic_activity("polonius_analysis");
175            Some(Box::new(Output::compute(polonius_facts, algorithm, false)))
176        } else {
177            None
178        }
179    });
180
181    // Solve the region constraints.
182    let (closure_region_requirements, nll_errors) =
183        regioncx.solve(infcx, body, polonius_output.clone());
184
185    NllOutput {
186        regioncx,
187        polonius_input: polonius_facts.map(Box::new),
188        polonius_output,
189        opt_closure_req: closure_region_requirements,
190        nll_errors,
191        polonius_diagnostics,
192    }
193}
194
195/// `-Zdump-mir=nll` dumps MIR annotated with NLL specific information:
196/// - free regions
197/// - inferred region values
198/// - region liveness
199/// - inference constraints and their causes
200///
201/// As well as graphviz `.dot` visualizations of:
202/// - the region constraints graph
203/// - the region SCC graph
204pub(super) fn dump_nll_mir<'tcx>(
205    infcx: &BorrowckInferCtxt<'tcx>,
206    body: &Body<'tcx>,
207    regioncx: &RegionInferenceContext<'tcx>,
208    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
209    borrow_set: &BorrowSet<'tcx>,
210) {
211    let tcx = infcx.tcx;
212    let Some(dumper) = MirDumper::new(tcx, "nll", body) else { return };
213
214    // We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in
215    // #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example,
216    // they're always disabled in mir-opt tests to make working with blessed dumps easier.
217    let options = PrettyPrintMirOptions {
218        include_extra_comments: matches!(
219            infcx.tcx.sess.opts.unstable_opts.mir_include_spans,
220            MirIncludeSpans::On | MirIncludeSpans::Nll
221        ),
222    };
223
224    let extra_data = &|pass_where, out: &mut dyn std::io::Write| {
225        emit_nll_mir(tcx, regioncx, closure_region_requirements, borrow_set, pass_where, out)
226    };
227
228    let dumper = dumper.set_extra_data(extra_data).set_options(options);
229
230    dumper.dump_mir(body);
231
232    // Also dump the region constraint graph as a graphviz file.
233    let _: io::Result<()> = try {
234        let mut file = dumper.create_dump_file("regioncx.all.dot", body)?;
235        regioncx.dump_graphviz_raw_constraints(tcx, &mut file)?;
236    };
237
238    // Also dump the region constraint SCC graph as a graphviz file.
239    let _: io::Result<()> = try {
240        let mut file = dumper.create_dump_file("regioncx.scc.dot", body)?;
241        regioncx.dump_graphviz_scc_constraints(tcx, &mut file)?;
242    };
243}
244
245/// Produces the actual NLL MIR sections to emit during the dumping process.
246pub(crate) fn emit_nll_mir<'tcx>(
247    tcx: TyCtxt<'tcx>,
248    regioncx: &RegionInferenceContext<'tcx>,
249    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
250    borrow_set: &BorrowSet<'tcx>,
251    pass_where: PassWhere,
252    out: &mut dyn io::Write,
253) -> io::Result<()> {
254    match pass_where {
255        // Before the CFG, dump out the values for each region variable.
256        PassWhere::BeforeCFG => {
257            regioncx.dump_mir(tcx, out)?;
258            writeln!(out, "|")?;
259
260            if let Some(closure_region_requirements) = closure_region_requirements {
261                writeln!(out, "| Free Region Constraints")?;
262                for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| {
263                    writeln!(out, "| {msg}")
264                })?;
265                writeln!(out, "|")?;
266            }
267
268            if borrow_set.len() > 0 {
269                writeln!(out, "| Borrows")?;
270                for (borrow_idx, borrow_data) in borrow_set.iter_enumerated() {
271                    writeln!(
272                        out,
273                        "| {:?}: issued at {:?} in {:?}",
274                        borrow_idx, borrow_data.reserve_location, borrow_data.region
275                    )?;
276                }
277                writeln!(out, "|")?;
278            }
279        }
280
281        PassWhere::BeforeLocation(_) => {}
282
283        PassWhere::AfterTerminator(_) => {}
284
285        PassWhere::BeforeBlock(_) | PassWhere::AfterLocation(_) | PassWhere::AfterCFG => {}
286    }
287    Ok(())
288}
289
290#[allow(rustc::diagnostic_outside_of_impl)]
291#[allow(rustc::untranslatable_diagnostic)]
292pub(super) fn dump_annotation<'tcx, 'infcx>(
293    infcx: &'infcx BorrowckInferCtxt<'tcx>,
294    body: &Body<'tcx>,
295    regioncx: &RegionInferenceContext<'tcx>,
296    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
297) {
298    let tcx = infcx.tcx;
299    let base_def_id = tcx.typeck_root_def_id(body.source.def_id());
300    if !tcx.has_attr(base_def_id, sym::rustc_regions) {
301        return;
302    }
303
304    // When the enclosing function is tagged with `#[rustc_regions]`,
305    // we dump out various bits of state as warnings. This is useful
306    // for verifying that the compiler is behaving as expected. These
307    // warnings focus on the closure region requirements -- for
308    // viewing the intraprocedural state, the -Zdump-mir output is
309    // better.
310
311    let def_span = tcx.def_span(body.source.def_id());
312    let err = if let Some(closure_region_requirements) = closure_region_requirements {
313        let mut err = infcx.dcx().struct_span_note(def_span, "external requirements");
314
315        regioncx.annotate(tcx, &mut err);
316
317        err.note(format!(
318            "number of external vids: {}",
319            closure_region_requirements.num_external_vids
320        ));
321
322        // Dump the region constraints we are imposing *between* those
323        // newly created variables.
324        for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| {
325            err.note(msg);
326            Ok(())
327        })
328        .unwrap();
329
330        err
331    } else {
332        let mut err = infcx.dcx().struct_span_note(def_span, "no external requirements");
333        regioncx.annotate(tcx, &mut err);
334        err
335    };
336
337    // FIXME(@lcnr): We currently don't dump the inferred hidden types here.
338    err.emit();
339}
340
341fn for_each_region_constraint<'tcx>(
342    tcx: TyCtxt<'tcx>,
343    closure_region_requirements: &ClosureRegionRequirements<'tcx>,
344    with_msg: &mut dyn FnMut(String) -> io::Result<()>,
345) -> io::Result<()> {
346    for req in &closure_region_requirements.outlives_requirements {
347        let subject = match req.subject {
348            ClosureOutlivesSubject::Region(subject) => format!("{subject:?}"),
349            ClosureOutlivesSubject::Ty(ty) => {
350                with_no_trimmed_paths!(format!(
351                    "{}",
352                    ty.instantiate(tcx, |vid| ty::Region::new_var(tcx, vid))
353                ))
354            }
355        };
356        with_msg(format!("where {}: {:?}", subject, req.outlived_free_region,))?;
357    }
358    Ok(())
359}
360
361pub(crate) trait ConstraintDescription {
362    fn description(&self) -> &'static str;
363}