Skip to content

Commit 922958c

Browse files
committed
Auto merge of #145244 - lcnr:handle-opaque-types-before-region-inference, r=BoxyUwU
support non-defining uses of opaques in borrowck Reimplements the first part of #139587, but limited to only the new solver. To do so I also entirely rewrite the way we handle opaque types in borrowck even on stable. This should not impact behavior however. We now support revealing uses during MIR borrowck with the new solver: ```rust fn foo<'a>(x: &'a u32) -> impl Sized + use<'a> { let local = 1; foo::<'_>(&local); x } ``` ### How do opaque types work right now Whenever we use an opaque type during type checking, we remember this use in the `opaque_type_storage` of the `InferCtxt`. Right now, we collect all *member constraints* at the end of MIR type check by looking at all uses from the `opaque_type_storage`. We then apply these constraints while computing the region values for each SCC. This does not add actual region constraints but directly updates the final region values. This means we need to manually handle any constraints from member constraints for diagnostics. We do this by separately tracking `applied_member_constraints` in the `RegionInferenceContext`. After we've finished computing the region values, it is now immutable and we check whether all member constraints hold. If not, we error. We now map the hidden types of our defining uses to the defining scope. This assumes that all member constraints apply. To handle non-member regions, we simply map any region in the hidden type we fail to map to a choice region to `'erased` https://github.com/rust-lang/rust/blob/b1b26b834d85e84b46aa8f8f3ce210a1627aa85f/compiler/rustc_borrowck/src/region_infer/opaque_types.rs#L126-L132 ### How do we handle opaque types with this PR MIR type check still works the same by populating the `opaque_type_storage` whenever we use an opaque type. We now have a new step `fn handle_opaque_type_uses` which happens between MIR type check and `compute_regions`. This step looks at all opaque type uses in the storage and first checks whether they are defining: are the arguments of the `opaque_type_key` unique region parameters. *With the new solver we silently ignore any *non-defining* uses here. The old solver emits an errors.* `fn compute_concrete_opaque_types`: We then collect all member constraints for the defining uses and apply them just like we did before. However, we do it on a temporary region graph which is only used while computing the concrete opaque types. We then use this region graph to compute the concrete type which we then store in the `root_cx`. `fn apply_computed_concrete_opaque_types`: Now that we know the final concrete type of each opaque type and have mapped them to the definition of the opaque. We iterate over all opaque type uses and equate their hidden type with the instantiated final concrete type. This is the step which actually mutates the region graph. The actual region checking can now entirely ignores opaque types (outside of the `ConstraintCategory` from checking the opaque type uses). ### Diagnostics issue (chill) Because we now simply use type equality to "apply member constraints" we get ordinary `OutlivesConstraint`s, even if the regions were already related to another. This is generally not an issue, expect that it can *hide* the actual region constraints which resulted in the final value of the opaque. The constraints we get from checking against the final opaque type definition relies on constraints we used to compute that definition. I mostly handle this by updating `find_constraint_path_between_regions` to first ignore member constraints in its search and only if that does not find a path, retry while considering member constraints. ### Diagnostics issue (not chill) A separate issue is that `find_constraint_paths_between_regions` currently looks up member constraints by their **scc**, not by region value: https://github.com/rust-lang/rust/blob/2c1ac85679678dfe5cce7ea8037735b0349ceaf3/compiler/rustc_borrowck/src/region_infer/mod.rs#L1768-L1775 This means that in the `borrowck-4` test, the resulting constraint path is currently ``` ('?2: '?5) due to Single(bb0[5]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)), ('?5: '?3) due to Single(bb0[6]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)), ('?3: '?0) due to All(src/main.rs:15:5: 15:6 (#0)) (None) (OpaqueType) (ConstraintSccIndex(1): ConstraintSccIndex(1)) ``` Here `'?3` is equal to `'?4`, but the reason why it's in the opaque is that it's related to `'?4`. With my PR this will be correctly tracked so we end up with ``` ('?2: '?5) due to Single(bb0[5]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)), ('?5: '?3) due to Single(bb0[6]) (None) (Boring) (ConstraintSccIndex(1): ConstraintSccIndex(1)), ('?3: '?4) due to Single(bb0[6]) (None) (Assignment) (ConstraintSccIndex(1): ConstraintSccIndex(1)), ('?4: '?0) due to All(src/main.rs:15:5: 15:6 (#0)) (None) (OpaqueType) (ConstraintSccIndex(1): ConstraintSccIndex(1)), ``` This additional `Assignment` step then worsens the error message as we stop talking about the fact that the closures is returned from the function. Fixing this is hard. I've looked into this and it's making me sad :< Properly handling this requires some deeper changes to MIR borrowck diagnostics and that seems like too much for this PR. Given that this only impacts a single test, it seems acceptable to me. r? `@ghost`
2 parents 125ff8a + c2a0fa8 commit 922958c

40 files changed

+1317
-1403
lines changed

compiler/rustc_borrowck/src/dataflow.rs

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::fmt;
22

33
use rustc_data_structures::fx::FxIndexMap;
4-
use rustc_data_structures::graph;
54
use rustc_index::bit_set::{DenseBitSet, MixedBitSet};
65
use rustc_middle::mir::{
76
self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges,
@@ -317,9 +316,8 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
317316
loans_out_of_scope_at_location: FxIndexMap::default(),
318317
};
319318
for (loan_idx, loan_data) in borrow_set.iter_enumerated() {
320-
let issuing_region = loan_data.region;
321319
let loan_issued_at = loan_data.reserve_location;
322-
prec.precompute_loans_out_of_scope(loan_idx, issuing_region, loan_issued_at);
320+
prec.precompute_loans_out_of_scope(loan_idx, loan_issued_at);
323321
}
324322

325323
prec.loans_out_of_scope_at_location
@@ -328,45 +326,7 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
328326
/// Loans are in scope while they are live: whether they are contained within any live region.
329327
/// In the location-insensitive analysis, a loan will be contained in a region if the issuing
330328
/// region can reach it in the subset graph. So this is a reachability problem.
331-
fn precompute_loans_out_of_scope(
332-
&mut self,
333-
loan_idx: BorrowIndex,
334-
issuing_region: RegionVid,
335-
loan_issued_at: Location,
336-
) {
337-
let sccs = self.regioncx.constraint_sccs();
338-
let universal_regions = self.regioncx.universal_regions();
339-
340-
// The loop below was useful for the location-insensitive analysis but shouldn't be
341-
// impactful in the location-sensitive case. It seems that it does, however, as without it a
342-
// handful of tests fail. That likely means some liveness or outlives data related to choice
343-
// regions is missing
344-
// FIXME: investigate the impact of loans traversing applied member constraints and why some
345-
// tests fail otherwise.
346-
//
347-
// We first handle the cases where the loan doesn't go out of scope, depending on the
348-
// issuing region's successors.
349-
for successor in graph::depth_first_search(&self.regioncx.region_graph(), issuing_region) {
350-
// Via applied member constraints
351-
//
352-
// The issuing region can flow into the choice regions, and they are either:
353-
// - placeholders or free regions themselves,
354-
// - or also transitively outlive a free region.
355-
//
356-
// That is to say, if there are applied member constraints here, the loan escapes the
357-
// function and cannot go out of scope. We could early return here.
358-
//
359-
// For additional insurance via fuzzing and crater, we verify that the constraint's min
360-
// choice indeed escapes the function. In the future, we could e.g. turn this check into
361-
// a debug assert and early return as an optimization.
362-
let scc = sccs.scc(successor);
363-
for constraint in self.regioncx.applied_member_constraints(scc) {
364-
if universal_regions.is_universal_region(constraint.min_choice) {
365-
return;
366-
}
367-
}
368-
}
369-
329+
fn precompute_loans_out_of_scope(&mut self, loan_idx: BorrowIndex, loan_issued_at: Location) {
370330
let first_block = loan_issued_at.block;
371331
let first_bb_data = &self.body.basic_blocks[first_block];
372332

compiler/rustc_borrowck/src/diagnostics/opaque_types.rs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use rustc_middle::mir::{self, ConstraintCategory, Location};
1313
use rustc_middle::ty::{
1414
self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
1515
};
16+
use rustc_span::Span;
17+
use rustc_trait_selection::error_reporting::infer::region::unexpected_hidden_region_diagnostic;
1618
use rustc_trait_selection::errors::impl_trait_overcapture_suggestion;
1719

1820
use crate::MirBorrowckCtxt;
@@ -26,13 +28,61 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
2628
if errors.is_empty() {
2729
return;
2830
}
31+
32+
let infcx = self.infcx;
2933
let mut guar = None;
34+
let mut last_unexpected_hidden_region: Option<(Span, Ty<'_>, ty::OpaqueTypeKey<'tcx>)> =
35+
None;
3036
for error in errors {
3137
guar = Some(match error {
32-
DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err) => err.report(self.infcx),
38+
DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err) => err.report(infcx),
3339
DeferredOpaqueTypeError::LifetimeMismatchOpaqueParam(err) => {
34-
self.infcx.dcx().emit_err(err)
40+
infcx.dcx().emit_err(err)
41+
}
42+
DeferredOpaqueTypeError::UnexpectedHiddenRegion {
43+
opaque_type_key,
44+
hidden_type,
45+
member_region,
46+
} => {
47+
let named_ty =
48+
self.regioncx.name_regions_for_member_constraint(infcx.tcx, hidden_type.ty);
49+
let named_key = self
50+
.regioncx
51+
.name_regions_for_member_constraint(infcx.tcx, opaque_type_key);
52+
let named_region =
53+
self.regioncx.name_regions_for_member_constraint(infcx.tcx, member_region);
54+
let diag = unexpected_hidden_region_diagnostic(
55+
infcx,
56+
self.mir_def_id(),
57+
hidden_type.span,
58+
named_ty,
59+
named_region,
60+
named_key,
61+
);
62+
if last_unexpected_hidden_region
63+
!= Some((hidden_type.span, named_ty, named_key))
64+
{
65+
last_unexpected_hidden_region =
66+
Some((hidden_type.span, named_ty, named_key));
67+
diag.emit()
68+
} else {
69+
diag.delay_as_bug()
70+
}
3571
}
72+
DeferredOpaqueTypeError::NonDefiningUseInDefiningScope {
73+
span,
74+
opaque_type_key,
75+
} => infcx.dcx().span_err(
76+
span,
77+
format!(
78+
"non-defining use of `{}` in the defining scope",
79+
Ty::new_opaque(
80+
infcx.tcx,
81+
opaque_type_key.def_id.to_def_id(),
82+
opaque_type_key.args
83+
)
84+
),
85+
),
3686
});
3787
}
3888
let guar = guar.unwrap();
@@ -194,7 +244,7 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for FindOpaqueRegion<'_, 'tcx> {
194244

195245
// Find a path between the borrow region and our opaque capture.
196246
if let Some((path, _)) =
197-
self.regioncx.find_constraint_paths_between_regions(self.borrow_region, |r| {
247+
self.regioncx.find_constraint_path_between_regions(self.borrow_region, |r| {
198248
r == opaque_region_vid
199249
})
200250
{

compiler/rustc_borrowck/src/diagnostics/region_errors.rs

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ use rustc_trait_selection::error_reporting::infer::nice_region_error::{
2323
self, HirTraitObjectVisitor, NiceRegionError, TraitObjectVisitor, find_anon_type,
2424
find_param_with_region, suggest_adding_lifetime_params,
2525
};
26-
use rustc_trait_selection::error_reporting::infer::region::unexpected_hidden_region_diagnostic;
2726
use rustc_trait_selection::infer::InferCtxtExt;
2827
use rustc_trait_selection::traits::{Obligation, ObligationCtxt};
2928
use tracing::{debug, instrument, trace};
@@ -105,18 +104,6 @@ pub(crate) enum RegionErrorKind<'tcx> {
105104
/// A generic bound failure for a type test (`T: 'a`).
106105
TypeTestError { type_test: TypeTest<'tcx> },
107106

108-
/// An unexpected hidden region for an opaque type.
109-
UnexpectedHiddenRegion {
110-
/// The span for the member constraint.
111-
span: Span,
112-
/// The hidden type.
113-
hidden_ty: Ty<'tcx>,
114-
/// The opaque type.
115-
key: ty::OpaqueTypeKey<'tcx>,
116-
/// The unexpected region.
117-
member_region: ty::Region<'tcx>,
118-
},
119-
120107
/// Higher-ranked subtyping error.
121108
BoundUniversalRegionError {
122109
/// The placeholder free region.
@@ -323,11 +310,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
323310
pub(crate) fn report_region_errors(&mut self, nll_errors: RegionErrors<'tcx>) {
324311
// Iterate through all the errors, producing a diagnostic for each one. The diagnostics are
325312
// buffered in the `MirBorrowckCtxt`.
326-
327313
let mut outlives_suggestion = OutlivesSuggestionBuilder::default();
328-
let mut last_unexpected_hidden_region: Option<(Span, Ty<'_>, ty::OpaqueTypeKey<'tcx>)> =
329-
None;
330-
331314
for (nll_error, _) in nll_errors.into_iter() {
332315
match nll_error {
333316
RegionErrorKind::TypeTestError { type_test } => {
@@ -378,30 +361,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
378361
}
379362
}
380363

381-
RegionErrorKind::UnexpectedHiddenRegion { span, hidden_ty, key, member_region } => {
382-
let named_ty =
383-
self.regioncx.name_regions_for_member_constraint(self.infcx.tcx, hidden_ty);
384-
let named_key =
385-
self.regioncx.name_regions_for_member_constraint(self.infcx.tcx, key);
386-
let named_region = self
387-
.regioncx
388-
.name_regions_for_member_constraint(self.infcx.tcx, member_region);
389-
let diag = unexpected_hidden_region_diagnostic(
390-
self.infcx,
391-
self.mir_def_id(),
392-
span,
393-
named_ty,
394-
named_region,
395-
named_key,
396-
);
397-
if last_unexpected_hidden_region != Some((span, named_ty, named_key)) {
398-
self.buffer_error(diag);
399-
last_unexpected_hidden_region = Some((span, named_ty, named_key));
400-
} else {
401-
diag.delay_as_bug();
402-
}
403-
}
404-
405364
RegionErrorKind::BoundUniversalRegionError {
406365
longer_fr,
407366
placeholder,

compiler/rustc_borrowck/src/handle_placeholders.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use tracing::debug;
1515
use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet};
1616
use crate::consumers::OutlivesConstraint;
1717
use crate::diagnostics::UniverseInfo;
18-
use crate::member_constraints::MemberConstraintSet;
1918
use crate::region_infer::values::{LivenessValues, PlaceholderIndices};
2019
use crate::region_infer::{ConstraintSccs, RegionDefinition, Representative, TypeTest};
2120
use crate::ty::VarianceDiagInfo;
@@ -30,7 +29,6 @@ pub(crate) struct LoweredConstraints<'tcx> {
3029
pub(crate) constraint_sccs: Sccs<RegionVid, ConstraintSccIndex>,
3130
pub(crate) definitions: Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>,
3231
pub(crate) scc_annotations: IndexVec<ConstraintSccIndex, RegionTracker>,
33-
pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>,
3432
pub(crate) outlives_constraints: Frozen<OutlivesConstraintSet<'tcx>>,
3533
pub(crate) type_tests: Vec<TypeTest<'tcx>>,
3634
pub(crate) liveness_constraints: LivenessValues,
@@ -147,9 +145,10 @@ impl scc::Annotation for RegionTracker {
147145

148146
/// Determines if the region variable definitions contain
149147
/// placeholders, and compute them for later use.
150-
fn region_definitions<'tcx>(
151-
universal_regions: &UniversalRegions<'tcx>,
148+
// FIXME: This is also used by opaque type handling. Move it to a separate file.
149+
pub(super) fn region_definitions<'tcx>(
152150
infcx: &BorrowckInferCtxt<'tcx>,
151+
universal_regions: &UniversalRegions<'tcx>,
153152
) -> (Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>, bool) {
154153
let var_infos = infcx.get_region_var_infos();
155154
// Create a RegionDefinition for each inference variable. This happens here because
@@ -213,14 +212,13 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>(
213212
infcx: &BorrowckInferCtxt<'tcx>,
214213
) -> LoweredConstraints<'tcx> {
215214
let universal_regions = &universal_region_relations.universal_regions;
216-
let (definitions, has_placeholders) = region_definitions(universal_regions, infcx);
215+
let (definitions, has_placeholders) = region_definitions(infcx, universal_regions);
217216

218217
let MirTypeckRegionConstraints {
219218
placeholder_indices,
220219
placeholder_index_to_region: _,
221220
liveness_constraints,
222221
mut outlives_constraints,
223-
member_constraints,
224222
universe_causes,
225223
type_tests,
226224
} = constraints;
@@ -246,7 +244,6 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>(
246244

247245
return LoweredConstraints {
248246
type_tests,
249-
member_constraints,
250247
constraint_sccs,
251248
scc_annotations: scc_annotations.scc_to_annotation,
252249
definitions,
@@ -283,7 +280,6 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>(
283280
constraint_sccs,
284281
definitions,
285282
scc_annotations,
286-
member_constraints,
287283
outlives_constraints: Frozen::freeze(outlives_constraints),
288284
type_tests,
289285
liveness_constraints,

compiler/rustc_borrowck/src/lib.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ mod dataflow;
7878
mod def_use;
7979
mod diagnostics;
8080
mod handle_placeholders;
81-
mod member_constraints;
8281
mod nll;
8382
mod path_utils;
8483
mod place_ext;
@@ -335,9 +334,10 @@ fn do_mir_borrowck<'tcx>(
335334

336335
// Run the MIR type-checker.
337336
let MirTypeckResults {
338-
constraints,
337+
mut constraints,
339338
universal_region_relations,
340-
opaque_type_values,
339+
region_bound_pairs,
340+
known_type_outlives_obligations,
341341
polonius_context,
342342
} = type_check::type_check(
343343
root_cx,
@@ -352,6 +352,17 @@ fn do_mir_borrowck<'tcx>(
352352
Rc::clone(&location_map),
353353
);
354354

355+
let opaque_type_errors = region_infer::opaque_types::handle_opaque_type_uses(
356+
root_cx,
357+
&infcx,
358+
&body,
359+
&universal_region_relations,
360+
&region_bound_pairs,
361+
&known_type_outlives_obligations,
362+
&location_map,
363+
&mut constraints,
364+
);
365+
355366
// Compute non-lexical lifetimes using the constraints computed
356367
// by typechecking the MIR body.
357368
let nll::NllOutput {
@@ -375,8 +386,6 @@ fn do_mir_borrowck<'tcx>(
375386
polonius_context,
376387
);
377388

378-
let opaque_type_errors = regioncx.infer_opaque_types(root_cx, &infcx, opaque_type_values);
379-
380389
// Dump MIR results into a file, if that is enabled. This lets us
381390
// write unit-tests, as well as helping with debugging.
382391
nll::dump_nll_mir(&infcx, body, &regioncx, &opt_closure_req, &borrow_set);

0 commit comments

Comments
 (0)