Skip to content

Commit 02a6662

Browse files
committed
Implement subset errors using Polonius
- switches to using the Naive variant by default - emits subset errors or propagates unsatisfied obligations to the caller
1 parent 7a3dca6 commit 02a6662

File tree

2 files changed

+172
-18
lines changed

2 files changed

+172
-18
lines changed

src/librustc_mir/borrow_check/nll/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ pub(in crate::borrow_check) fn compute_regions<'cx, 'tcx>(
300300

301301
if infcx.tcx.sess.opts.debugging_opts.polonius {
302302
let algorithm = env::var("POLONIUS_ALGORITHM")
303-
.unwrap_or_else(|_| String::from("Hybrid"));
303+
.unwrap_or_else(|_| String::from("Naive"));
304304
let algorithm = Algorithm::from_str(&algorithm).unwrap();
305305
debug!("compute_regions: using polonius algorithm {:?}", algorithm);
306306
Some(Rc::new(Output::compute(
@@ -315,7 +315,7 @@ pub(in crate::borrow_check) fn compute_regions<'cx, 'tcx>(
315315

316316
// Solve the region constraints.
317317
let closure_region_requirements =
318-
regioncx.solve(infcx, &body, local_names, upvars, def_id, errors_buffer);
318+
regioncx.solve(infcx, &body, local_names, upvars, def_id, errors_buffer, polonius_output.clone());
319319

320320
// Dump MIR results into a file, if that is enabled. This let us
321321
// write unit-tests, as well as helping with debugging.

src/librustc_mir/borrow_check/nll/region_infer/mod.rs

Lines changed: 170 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use crate::borrow_check::{
4444

4545
use self::values::{LivenessValues, RegionValueElements, RegionValues};
4646
use super::universal_regions::UniversalRegions;
47-
use super::ToRegionVid;
47+
use super::{PoloniusOutput, ToRegionVid};
4848

4949
mod dump_mir;
5050
mod graphviz;
@@ -484,6 +484,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
484484
upvars: &[Upvar],
485485
mir_def_id: DefId,
486486
errors_buffer: &mut Vec<Diagnostic>,
487+
polonius_output: Option<Rc<PoloniusOutput>>,
487488
) -> Option<ClosureRegionRequirements<'tcx>> {
488489
self.propagate_constraints(body);
489490

@@ -509,16 +510,33 @@ impl<'tcx> RegionInferenceContext<'tcx> {
509510
// multiple problems.
510511
let mut region_naming = RegionErrorNamingCtx::new();
511512

512-
self.check_universal_regions(
513-
infcx,
514-
body,
515-
local_names,
516-
upvars,
517-
mir_def_id,
518-
outlives_requirements.as_mut(),
519-
errors_buffer,
520-
&mut region_naming,
521-
);
513+
// In Polonius mode, the errors about missing universal region relations are in the output
514+
// and need to be emitted or propagated. Otherwise, we need to check whether the
515+
// constraints were too strong, and if so, emit or propagate those errors.
516+
if infcx.tcx.sess.opts.debugging_opts.polonius {
517+
self.check_polonius_subset_errors(
518+
infcx,
519+
body,
520+
local_names,
521+
upvars,
522+
mir_def_id,
523+
outlives_requirements.as_mut(),
524+
errors_buffer,
525+
&mut region_naming,
526+
polonius_output.expect("Polonius output is unavailable despite `-Z polonius`"),
527+
);
528+
} else {
529+
self.check_universal_regions(
530+
infcx,
531+
body,
532+
local_names,
533+
upvars,
534+
mir_def_id,
535+
outlives_requirements.as_mut(),
536+
errors_buffer,
537+
&mut region_naming,
538+
);
539+
}
522540

523541
self.check_member_constraints(infcx, mir_def_id, errors_buffer);
524542

@@ -1375,6 +1393,111 @@ impl<'tcx> RegionInferenceContext<'tcx> {
13751393
outlives_suggestion.add_suggestion(body, self, infcx, errors_buffer, region_naming);
13761394
}
13771395

1396+
/// Checks if Polonius has found any unexpected free region relations.
1397+
///
1398+
/// In Polonius terms, a "subset error" (or "illegal subset relation error") is the equivalent
1399+
/// of NLL's "checking if any region constraints were too strong": a placeholder origin `'a`
1400+
/// was unexpectedly found to be a subset of another placeholder origin `'b`, and means in NLL
1401+
/// terms that the "longer free region" `'a` outlived the "shorter free region" `'b`.
1402+
///
1403+
/// More details can be found in this blog post by Niko:
1404+
/// http://smallcultfollowing.com/babysteps/blog/2019/01/17/polonius-and-region-errors/
1405+
///
1406+
/// In the canonical example
1407+
///
1408+
/// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
1409+
///
1410+
/// returning `x` requires `&'a u32 <: &'b u32` and hence we establish (transitively) a
1411+
/// constraint that `'a: 'b`. It is an error that we have no evidence that this
1412+
/// constraint holds.
1413+
///
1414+
/// If `propagated_outlives_requirements` is `Some`, then we will
1415+
/// push unsatisfied obligations into there. Otherwise, we'll
1416+
/// report them as errors.
1417+
fn check_polonius_subset_errors(
1418+
&self,
1419+
infcx: &InferCtxt<'_, 'tcx>,
1420+
body: &Body<'tcx>,
1421+
local_names: &IndexVec<Local, Option<Symbol>>,
1422+
upvars: &[Upvar],
1423+
mir_def_id: DefId,
1424+
mut propagated_outlives_requirements: Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
1425+
errors_buffer: &mut Vec<Diagnostic>,
1426+
region_naming: &mut RegionErrorNamingCtx,
1427+
polonius_output: Rc<PoloniusOutput>,
1428+
) {
1429+
debug!("check_polonius_subset_errors: {} subset_errors", polonius_output.subset_errors.len());
1430+
1431+
let mut outlives_suggestion = OutlivesSuggestionBuilder::new(mir_def_id, local_names);
1432+
1433+
// Similarly to `check_universal_regions`: a free region relation, which was not explicitly
1434+
// declared ("known") was found by Polonius, so emit an error, or propagate the
1435+
// requirements for our caller into the `propagated_outlives_requirements` vector.
1436+
//
1437+
// Polonius doesn't model regions ("origins") as CFG-subsets or durations, but the
1438+
// `longer_fr` and `shorter_fr` terminology will still be used here, for consistency with
1439+
// the rest of the NLL infrastructure. The "subset origin" is the "longer free region",
1440+
// and the "superset origin" is the outlived "shorter free region".
1441+
//
1442+
// Note: Polonius will produce a subset error at every point where the unexpected
1443+
// `longer_fr`'s "placeholder loan" is contained in the `shorter_fr`. This can be helpful
1444+
// for diagnostics in the future, e.g. to point more precisely at the key locations
1445+
// requiring this constraint to hold. However, the error and diagnostics code downstream
1446+
// expects that these errors are not duplicated (and that they are in a certain order).
1447+
// Otherwise, diagnostics messages such as the ones giving names like `'1` to elided or
1448+
// anonymous lifetimes for example, could give these names differently, while others like
1449+
// the outlives suggestions or the debug output from `#[rustc_regions]` would be
1450+
// duplicated. The polonius subset errors are deduplicated here, while keeping the
1451+
// CFG-location ordering.
1452+
let mut subset_errors: Vec<_> = polonius_output
1453+
.subset_errors
1454+
.iter()
1455+
.flat_map(|(_location, subset_errors)| subset_errors.iter())
1456+
.collect();
1457+
subset_errors.sort();
1458+
subset_errors.dedup();
1459+
1460+
for (longer_fr, shorter_fr) in subset_errors.into_iter() {
1461+
debug!("check_polonius_subset_errors: subset_error longer_fr={:?},\
1462+
shorter_fr={:?}", longer_fr, shorter_fr);
1463+
1464+
self.report_or_propagate_universal_region_error(
1465+
*longer_fr,
1466+
*shorter_fr,
1467+
infcx,
1468+
body,
1469+
local_names,
1470+
upvars,
1471+
mir_def_id,
1472+
&mut propagated_outlives_requirements,
1473+
&mut outlives_suggestion,
1474+
errors_buffer,
1475+
region_naming,
1476+
);
1477+
}
1478+
1479+
// Handle the placeholder errors as usual, until the chalk-rustc-polonius triumvirate has
1480+
// a more complete picture on how to separate this responsibility.
1481+
for (fr, fr_definition) in self.definitions.iter_enumerated() {
1482+
match fr_definition.origin {
1483+
NLLRegionVariableOrigin::FreeRegion => {
1484+
// handled by polonius above
1485+
}
1486+
1487+
NLLRegionVariableOrigin::Placeholder(placeholder) => {
1488+
self.check_bound_universal_region(infcx, body, mir_def_id, fr, placeholder);
1489+
}
1490+
1491+
NLLRegionVariableOrigin::Existential { .. } => {
1492+
// nothing to check here
1493+
}
1494+
}
1495+
}
1496+
1497+
// Emit outlives suggestions
1498+
outlives_suggestion.add_suggestion(body, self, infcx, errors_buffer, region_naming);
1499+
}
1500+
13781501
/// Checks the final value for the free region `fr` to see if it
13791502
/// grew too large. In particular, examine what `end(X)` points
13801503
/// wound up in `fr`'s final value; for each `end(X)` where `X !=
@@ -1474,8 +1597,37 @@ impl<'tcx> RegionInferenceContext<'tcx> {
14741597
return None;
14751598
}
14761599

1600+
self.report_or_propagate_universal_region_error(
1601+
longer_fr,
1602+
shorter_fr,
1603+
infcx,
1604+
body,
1605+
local_names,
1606+
upvars,
1607+
mir_def_id,
1608+
propagated_outlives_requirements,
1609+
outlives_suggestion,
1610+
errors_buffer,
1611+
region_naming,
1612+
)
1613+
}
1614+
1615+
fn report_or_propagate_universal_region_error(
1616+
&self,
1617+
longer_fr: RegionVid,
1618+
shorter_fr: RegionVid,
1619+
infcx: &InferCtxt<'_, 'tcx>,
1620+
body: &Body<'tcx>,
1621+
local_names: &IndexVec<Local, Option<Symbol>>,
1622+
upvars: &[Upvar],
1623+
mir_def_id: DefId,
1624+
propagated_outlives_requirements: &mut Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
1625+
outlives_suggestion: &mut OutlivesSuggestionBuilder<'_>,
1626+
errors_buffer: &mut Vec<Diagnostic>,
1627+
region_naming: &mut RegionErrorNamingCtx,
1628+
) -> Option<ErrorReported> {
14771629
debug!(
1478-
"check_universal_region_relation: fr={:?} does not outlive shorter_fr={:?}",
1630+
"report_or_propagate_universal_region_error: fr={:?} does not outlive shorter_fr={:?}",
14791631
longer_fr, shorter_fr,
14801632
);
14811633

@@ -1484,9 +1636,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
14841636
// We'll call it `fr-` -- it's ever so slightly smaller than
14851637
// `longer_fr`.
14861638

1487-
if let Some(fr_minus) = self.universal_region_relations.non_local_lower_bound(longer_fr)
1488-
{
1489-
debug!("check_universal_region: fr_minus={:?}", fr_minus);
1639+
if let Some(fr_minus) =
1640+
self.universal_region_relations.non_local_lower_bound(longer_fr) {
1641+
debug!("report_or_propagate_universal_region_error: fr_minus={:?}", fr_minus);
14901642

14911643
let blame_span_category =
14921644
self.find_outlives_blame_span(body, longer_fr,
@@ -1497,7 +1649,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
14971649
// so slightly larger than `shorter_fr`.
14981650
let shorter_fr_plus =
14991651
self.universal_region_relations.non_local_upper_bounds(&shorter_fr);
1500-
debug!("check_universal_region: shorter_fr_plus={:?}", shorter_fr_plus);
1652+
debug!(
1653+
"report_or_propagate_universal_region_error: shorter_fr_plus={:?}", shorter_fr_plus
1654+
);
15011655
for &&fr in &shorter_fr_plus {
15021656
// Push the constraint `fr-: shorter_fr+`
15031657
propagated_outlives_requirements.push(ClosureOutlivesRequirement {

0 commit comments

Comments
 (0)