Skip to content

Commit 02e1f20

Browse files
committed
Auto merge of #151510 - LorrensP-2158466:wf-closure-arg, r=<try>
WF checks on closure arguments.
2 parents 36714a9 + 5581cea commit 02e1f20

23 files changed

Lines changed: 281 additions & 128 deletions

compiler/rustc_borrowck/src/region_infer/mod.rs

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
585585
fn check_type_tests(
586586
&self,
587587
infcx: &InferCtxt<'tcx>,
588-
mut propagated_outlives_requirements: Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
588+
propagated_outlives_requirements: Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
589589
errors_buffer: &mut RegionErrors<'tcx>,
590590
) {
591591
let tcx = infcx.tcx;
@@ -595,6 +595,13 @@ impl<'tcx> RegionInferenceContext<'tcx> {
595595
// the user. Avoid that.
596596
let mut deduplicate_errors = FxIndexSet::default();
597597

598+
// Each type test introduces one or more OR-constraints (e.g. T: 'a OR T: 'b),
599+
// where at least one option in each constraint must be satisfied. All such
600+
// constraints must be satisfied simultaneously: i.e., they form a conjunction (AND).
601+
// We'll use this conjunctive requirement later on.
602+
let mut conjunctive_propagated_outlives_requirement =
603+
propagated_outlives_requirements.is_some().then_some(vec![]);
604+
598605
for type_test in &self.type_tests {
599606
debug!("check_type_test: {:?}", type_test);
600607

@@ -608,8 +615,13 @@ impl<'tcx> RegionInferenceContext<'tcx> {
608615
continue;
609616
}
610617

611-
if let Some(propagated_outlives_requirements) = &mut propagated_outlives_requirements
612-
&& self.try_promote_type_test(infcx, type_test, propagated_outlives_requirements)
618+
if let Some(conjunctive_propagated_outlives_requirements) =
619+
&mut conjunctive_propagated_outlives_requirement
620+
&& self.try_promote_type_test(
621+
infcx,
622+
type_test,
623+
conjunctive_propagated_outlives_requirements,
624+
)
613625
{
614626
continue;
615627
}
@@ -633,6 +645,85 @@ impl<'tcx> RegionInferenceContext<'tcx> {
633645
errors_buffer.push(RegionErrorKind::TypeTestError { type_test: type_test.clone() });
634646
}
635647
}
648+
649+
if let Some(mut conjunctive_requirement) = conjunctive_propagated_outlives_requirement
650+
&& !conjunctive_requirement.is_empty()
651+
{
652+
// We can simplify this list of list of requirements.
653+
//
654+
// Say we did some number of type tests and it results in following requirements:
655+
//
656+
// R1: (T: 'a OR T: 'b)
657+
// R2: (T: 'a)
658+
//
659+
// * See `try_promote_type_test` below on why we obtain OR requirements implicitly.
660+
//
661+
// Full requirement is then: R1 AND R2. *BUT*, we can remove R1 entirely, because we already
662+
// require `T: 'a`, which implies `T:'a OR T: 'b`, making R1 redundant.
663+
//
664+
// The requirements can be seen as a boolean conjunctive normal form expression:
665+
// Treat a requirement `T: 'region` as a boolean value, then this problem is (almost)
666+
// equivalent to "Unit Propagation". However, this problem we are trying to solve is much
667+
// simpler: Unit Propagation considers any form of subexpression, even containing negation
668+
// of values, making it a multi-pass algorithm. The only subexpressions we encounter are of
669+
// the form (R1 OR ... OR RN), thus if even on R is required on their own (a unit), this
670+
// whole subexpression can be removed.
671+
//
672+
// Because of the outlives relations, we can actually have a stronger redundancy check,
673+
// say we have following requirements that create a conjunctive requirement:
674+
// R1: T: 'a
675+
// R2: T: 'b OR T: 'c
676+
// R: R1 AND R2
677+
//
678+
// And we have we an assumption in our environment that `'a: 'b`, we can thus remove R2
679+
// as well. `T: 'b` is implied by `T: 'a` because of the assumption `'a: 'b`:
680+
// T -> 'a -> 'b
681+
//
682+
// So we can filter redundant OR requirements with the following algorithm:
683+
// Collect every Unit requirement. Then for every OR requirement, loop over its
684+
// individual requirements and if the region is outlived by the region of one of the
685+
// units, remove the entire OR requirement.
686+
687+
fn requirement_key<'a>(subject: ClosureOutlivesRequirement<'a>) -> (Ty<'a>, RegionVid) {
688+
let ClosureOutlivesSubject::Ty(ClosureOutlivesSubjectTy { inner: ty }) =
689+
subject.subject
690+
else {
691+
unreachable!("ClosureOutliveSubject of a type test is always a Ty");
692+
};
693+
(ty, subject.outlived_free_region)
694+
}
695+
696+
let units: Vec<_> = conjunctive_requirement
697+
.iter()
698+
.filter_map(|r| {
699+
let [r] = r.as_slice() else { return None };
700+
Some(requirement_key(*r))
701+
})
702+
.collect();
703+
704+
// Remove the `or_requirement`s that contain any of the unit requirements.
705+
conjunctive_requirement.retain(|or_requirement| {
706+
or_requirement.len() == 1
707+
|| !or_requirement.iter().any(|r| {
708+
let (ty, region) = requirement_key(*r);
709+
units.iter().any(|&(unit_subj, unit_region)| {
710+
// Same type, and the unit region outlives the disjunct region,
711+
// meaning T: unit_region implies T: region.
712+
unit_subj == ty
713+
&& self.universal_region_relations.outlives(unit_region, region)
714+
})
715+
})
716+
});
717+
718+
assert!(
719+
!conjunctive_requirement.is_empty(),
720+
"It should not be possible to remove every requirement."
721+
);
722+
// Propagate all requirements as is.
723+
propagated_outlives_requirements
724+
.expect("conjunctive_requirements is `Some`, so this should be as well")
725+
.extend(conjunctive_requirement.into_iter().flatten());
726+
}
636727
}
637728

638729
/// Invoked when we have some type-test (e.g., `T: 'X`) that we cannot
@@ -664,7 +755,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
664755
&self,
665756
infcx: &InferCtxt<'tcx>,
666757
type_test: &TypeTest<'tcx>,
667-
propagated_outlives_requirements: &mut Vec<ClosureOutlivesRequirement<'tcx>>,
758+
propagated_outlives_requirements: &mut Vec<Vec<ClosureOutlivesRequirement<'tcx>>>,
668759
) -> bool {
669760
let tcx = infcx.tcx;
670761
let TypeTest { generic_kind, lower_bound, span: blame_span, verify_bound: _ } = *type_test;
@@ -690,24 +781,41 @@ impl<'tcx> RegionInferenceContext<'tcx> {
690781
if let Some(p) = self.scc_values.placeholders_contained_in(r_scc).next() {
691782
debug!("encountered placeholder in higher universe: {:?}, requiring 'static", p);
692783
let static_r = self.universal_regions().fr_static;
693-
propagated_outlives_requirements.push(ClosureOutlivesRequirement {
784+
propagated_outlives_requirements.push(vec![ClosureOutlivesRequirement {
694785
subject,
695786
outlived_free_region: static_r,
696787
blame_span,
697788
category: ConstraintCategory::Boring,
698-
});
789+
}]);
699790

700791
// we can return here -- the code below might push add'l constraints
701792
// but they would all be weaker than this one.
702793
return true;
703794
}
704795

705-
// For each region outlived by lower_bound find a non-local,
706-
// universal region (it may be the same region) and add it to
707-
// `ClosureOutlivesRequirement`.
708-
let mut found_outlived_universal_region = false;
709-
for ur in self.scc_values.universal_regions_outlived_by(r_scc) {
710-
found_outlived_universal_region = true;
796+
let universal_regions: Vec<_> =
797+
self.scc_values.universal_regions_outlived_by(r_scc).collect();
798+
debug!(?universal_regions);
799+
800+
// Filter to only the "minimal" universal regions:
801+
// Drop any region `a` that strictly outlives another region `b`.
802+
let minimal_universal_regions: Vec<_> = universal_regions
803+
.iter()
804+
.copied()
805+
.filter(|&a| {
806+
!universal_regions.iter().copied().any(|b| {
807+
!self.universal_region_relations.outlives(a, b)
808+
&& self.universal_region_relations.outlives(b, a)
809+
})
810+
})
811+
.collect();
812+
813+
assert!(
814+
!minimal_universal_regions.is_empty(),
815+
"There should always be at least 1 minimal region"
816+
);
817+
818+
for ur in minimal_universal_regions {
711819
debug!("universal_region_outlived_by ur={:?}", ur);
712820
let non_local_ub = self.universal_region_relations.non_local_upper_bounds(ur);
713821
debug!(?non_local_ub);
@@ -716,6 +824,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
716824
// and `'3: '1` we only need to prove that T: '2 *or* T: '3, but to
717825
// avoid potential non-determinism we approximate this by requiring
718826
// T: '1 and T: '2.
827+
let mut or_requirements = Vec::with_capacity(non_local_ub.len());
719828
for upper_bound in non_local_ub {
720829
debug_assert!(self.universal_regions().is_universal_region(upper_bound));
721830
debug_assert!(!self.universal_regions().is_local_free_region(upper_bound));
@@ -727,14 +836,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
727836
category: ConstraintCategory::Boring,
728837
};
729838
debug!(?requirement, "adding closure requirement");
730-
propagated_outlives_requirements.push(requirement);
839+
or_requirements.push(requirement);
731840
}
841+
propagated_outlives_requirements.push(or_requirements);
732842
}
733-
// If we succeed to promote the subject, i.e. it only contains non-local regions,
734-
// and fail to prove the type test inside of the closure, the `lower_bound` has to
735-
// also be at least as large as some universal region, as the type test is otherwise
736-
// trivial.
737-
assert!(found_outlived_universal_region);
738843
true
739844
}
740845

compiler/rustc_borrowck/src/type_check/canonical.rs

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::fmt;
33
use rustc_errors::ErrorGuaranteed;
44
use rustc_infer::infer::canonical::Canonical;
55
use rustc_infer::infer::outlives::env::RegionBoundPairs;
6-
use rustc_middle::bug;
76
use rustc_middle::mir::{Body, ConstraintCategory};
87
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, Unnormalized, Upcast};
98
use rustc_span::Span;
@@ -259,53 +258,4 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
259258
.and(type_op::ascribe_user_type::AscribeUserType { mir_ty, user_ty }),
260259
);
261260
}
262-
263-
/// *Incorrectly* skips the WF checks we normally do in `ascribe_user_type`.
264-
///
265-
/// FIXME(#104478, #104477): This is a hack for backward-compatibility.
266-
#[instrument(skip(self), level = "debug")]
267-
pub(super) fn ascribe_user_type_skip_wf(
268-
&mut self,
269-
mir_ty: Ty<'tcx>,
270-
user_ty: ty::UserType<'tcx>,
271-
span: Span,
272-
) {
273-
let ty::UserTypeKind::Ty(user_ty) = user_ty.kind else { bug!() };
274-
275-
// A fast path for a common case with closure input/output types.
276-
if let ty::Infer(_) = user_ty.kind() {
277-
self.eq_types(user_ty, mir_ty, Locations::All(span), ConstraintCategory::Boring)
278-
.unwrap();
279-
return;
280-
}
281-
282-
// This is a hack. `body.local_decls` are not necessarily normalized in the old
283-
// solver due to not deeply normalizing in writeback. So we must re-normalize here.
284-
//
285-
// I am not sure of a test case where this actually matters. There is a similar
286-
// hack in `equate_inputs_and_outputs` which does have associated test cases.
287-
let mir_ty = match self.infcx.next_trait_solver() {
288-
true => mir_ty,
289-
false => self.normalize(Unnormalized::new_wip(mir_ty), Locations::All(span)),
290-
};
291-
292-
let cause = ObligationCause::dummy_with_span(span);
293-
let param_env = self.infcx.param_env;
294-
let _: Result<_, ErrorGuaranteed> = self.fully_perform_op(
295-
Locations::All(span),
296-
ConstraintCategory::Boring,
297-
type_op::custom::CustomTypeOp::new(
298-
|ocx| {
299-
// The `AscribeUserType` query would normally emit a wf
300-
// obligation for the unnormalized user_ty here. This is
301-
// where the "incorrectly skips the WF checks we normally do"
302-
// happens
303-
let user_ty = ocx.normalize(&cause, param_env, Unnormalized::new_wip(user_ty));
304-
ocx.eq(&cause, param_env, user_ty, mir_ty)?;
305-
Ok(())
306-
},
307-
"ascribe_user_type_skip_wf",
308-
),
309-
);
310-
}
311261
}

compiler/rustc_borrowck/src/type_check/input_output.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
118118
.skip(1 + if is_coroutine_with_implicit_resume_ty { 1 } else { 0 })
119119
.map(|local| &self.body.local_decls[local]),
120120
) {
121-
self.ascribe_user_type_skip_wf(
121+
self.ascribe_user_type(
122122
arg_decl.ty,
123123
ty::UserType::new(ty::UserTypeKind::Ty(user_ty)),
124124
arg_decl.source_info.span,
@@ -127,7 +127,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
127127

128128
// If the user explicitly annotated the output type, enforce it.
129129
let output_decl = &self.body.local_decls[RETURN_PLACE];
130-
self.ascribe_user_type_skip_wf(
130+
self.ascribe_user_type(
131131
output_decl.ty,
132132
ty::UserType::new(ty::UserTypeKind::Ty(user_provided_sig.output())),
133133
output_decl.source_info.span,

tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ impl Foo {
1313
//~| ERROR the parameter type `impl for<'a> Fn(&'a usize) -> Box<I>` may not live long enough
1414
//~| ERROR the parameter type `impl for<'a> Fn(&'a usize) -> Box<I>` may not live long enough
1515
//~| ERROR the parameter type `I` may not live long enough
16-
//~| ERROR the parameter type `I` may not live long enough
17-
//~| ERROR the parameter type `I` may not live long enough
1816
//~| ERROR `f` does not live long enough
1917
}
2018
}

tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -69,34 +69,6 @@ help: consider adding an explicit lifetime bound
6969
LL | pub fn ack<I: 'static>(&mut self, f: impl for<'a> Fn(&'a usize) -> Box<I>) {
7070
| +++++++++
7171

72-
error[E0310]: the parameter type `I` may not live long enough
73-
--> $DIR/unconstrained-closure-lifetime-generic.rs:10:35
74-
|
75-
LL | self.bar = Box::new(|baz| Box::new(f(baz)));
76-
| ^^^^^^^^^^^^^^^^
77-
| |
78-
| the parameter type `I` must be valid for the static lifetime...
79-
| ...so that the type `I` will meet its required lifetime bounds
80-
|
81-
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
82-
help: consider adding an explicit lifetime bound
83-
|
84-
LL | pub fn ack<I: 'static>(&mut self, f: impl for<'a> Fn(&'a usize) -> Box<I>) {
85-
| +++++++++
86-
87-
error[E0311]: the parameter type `I` may not live long enough
88-
--> $DIR/unconstrained-closure-lifetime-generic.rs:10:35
89-
|
90-
LL | pub fn ack<I>(&mut self, f: impl for<'a> Fn(&'a usize) -> Box<I>) {
91-
| --------- the parameter type `I` must be valid for the anonymous lifetime defined here...
92-
LL | self.bar = Box::new(|baz| Box::new(f(baz)));
93-
| ^^^^^^^^^^^^^^^^ ...so that the type `I` will meet its required lifetime bounds
94-
|
95-
help: consider adding an explicit lifetime bound
96-
|
97-
LL | pub fn ack<'a, I: 'a>(&'a mut self, f: impl for<'a> Fn(&'a usize) -> Box<I>) {
98-
| +++ ++++ ++
99-
10072
error[E0597]: `f` does not live long enough
10173
--> $DIR/unconstrained-closure-lifetime-generic.rs:10:44
10274
|
@@ -113,7 +85,7 @@ LL | }
11385
|
11486
= note: due to object lifetime defaults, `Box<dyn for<'a> Fn(&'a usize) -> Box<(dyn Any + 'a)>>` actually means `Box<(dyn for<'a> Fn(&'a usize) -> Box<(dyn Any + 'a)> + 'static)>`
11587

116-
error: aborting due to 8 previous errors
88+
error: aborting due to 6 previous errors
11789

118-
Some errors have detailed explanations: E0310, E0311, E0597.
90+
Some errors have detailed explanations: E0310, E0597.
11991
For more information about an error, try `rustc --explain E0310`.

tests/ui/consts/issue-102117.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ impl VTable {
1616
layout: Layout::new::<T>(),
1717
type_id: TypeId::of::<T>(),
1818
//~^ ERROR the parameter type `T` may not live long enough
19-
//~| ERROR the parameter type `T` may not live long enough
2019
drop_in_place: unsafe {
2120
transmute::<unsafe fn(*mut T), unsafe fn(*mut ())>(drop_in_place::<T>)
2221
},

tests/ui/consts/issue-102117.stderr

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,6 @@ help: consider adding an explicit lifetime bound
1212
LL | pub fn new<T: 'static>() -> &'static Self {
1313
| +++++++++
1414

15-
error[E0310]: the parameter type `T` may not live long enough
16-
--> $DIR/issue-102117.rs:17:26
17-
|
18-
LL | type_id: TypeId::of::<T>(),
19-
| ^^^^^^^^^^^^^^^
20-
| |
21-
| the parameter type `T` must be valid for the static lifetime...
22-
| ...so that the type `T` will meet its required lifetime bounds
23-
|
24-
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
25-
help: consider adding an explicit lifetime bound
26-
|
27-
LL | pub fn new<T: 'static>() -> &'static Self {
28-
| +++++++++
29-
30-
error: aborting due to 2 previous errors
15+
error: aborting due to 1 previous error
3116

3217
For more information about this error, try `rustc --explain E0310`.

tests/ui/implied-bounds/hrlt-implied-trait-bounds-roundtrip.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
//@ check-pass
21
struct Foo<'a>(&'a ())
32
where
43
(): Trait<'a>;
@@ -21,7 +20,7 @@ where
2120
}
2221

2322
fn main() {
24-
let bar: for<'a, 'b> fn(Foo<'a>, &'b ()) = |_, _| {};
23+
let bar: for<'a, 'b> fn(Foo<'a>, &'b ()) = |_, _| {}; //~ ERROR: lifetime may not live long enough
2524

2625
// If `could_use_implied_bounds` were to use implied bounds,
2726
// keeping 'a late-bound, then we could assign that function
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: lifetime may not live long enough
2+
--> $DIR/hrlt-implied-trait-bounds-roundtrip.rs:23:49
3+
|
4+
LL | let bar: for<'a, 'b> fn(Foo<'a>, &'b ()) = |_, _| {};
5+
| ^
6+
| |
7+
| has type `Foo<'1>`
8+
| requires that `'1` must outlive `'static`
9+
10+
error: aborting due to 1 previous error
11+

0 commit comments

Comments
 (0)