Change:
Update: I originally thought it's a soundness issue, but it may not be, see comment This code compiles after ~although it shouldn't~: () label C-bug T-types regression-from-stable-to-nightly I-unsound A-NLL
WG-prioritization assigning priority (). label -I-prioritize +P-critical
I have yet to find a way to exploit this code for actual unsound behavior. Perhaps you know a way Preliminary experiments make it seem … possibly … more as if the call kinda ignores the explicit and infers a shorter lifetime instead.
E.g. this works but this doesn’t Same story with vs
I feel like the “predicate containing ” makes early-bound instead of late-bound which is probably what makes “a difference” here in terms of compiler behavior.
Early-bound vs late-bound makes sense for the change in behavior
I’m increasingly convinced that this is not really a soundness issue. The behavior is confusing though, so it is an issue. It’s almost as if the closure struct implementing the closure (which would be necessarily generic over the lifetimes it “captures” if implemented manually) did not get instantiated with the right lifetimes, but instead those were left inferred. I.e. would turn into something like if implemented by hand, but it currently behaves as if instead of something like is used, leaving the lifetime inferred/unspecified, and meaning that an error due to the constraint only appears if the calls (or other usages of the closure) put bounds on that inferred lifetime that are in conflict with the fact that is not true.
I agree that this is most likely not a soundness issue, but it's still confusing and fixing it in the future would be a breaking change. By looking closer at the source, the named lifetime that appears in the closure is indeed a free inference variable independent from the found in the defining scope. It behaves similarly to how theorized in the previous comment. Perhaps this counts an unintended stabilization. Re-requesting prioritization in light of this. label -I-unsound +I-prioritize
Prioritization assessment still holds (discussion on ). label -I-prioritize
I haven’t more thoroughly tested it, but adding bounds for all early-bound lifetime arguments of a function for a trait as in: seems to “fix” the problem (of incorrectly accepting this function). If code after this transformation does indeed behave as desired, all that would be necessary is to identify what needs to be changed so it behaves as if such a dummy trait bound was present. Edit: Nevermind, this breaks down quickly, e.g. even for this example, being systematic about the thing as in will not compile, despite the fact that the original code does (and should) compile. Nonetheless, I still think it’s an interesting observation that adding such a trait bound (only to so it is not even used for calling ) somehow makes the code (“successfully”) fail again.

Discussion:
); } <ins> // Now equate closure substs to regions inherited from `typeck_root_def_id`. Fixes #98589. let typeck_root_def_id = tcx.typeck_root_def_id(self.body.source.def_id()); let typeck_root_substs = ty::InternalSubsts::identity_for_item(tcx, typeck_root_def_id); let parent_substs = match tcx.def_kind(def_id) { DefKind::Closure => substs.as_closure().parent_substs(), DefKind::Generator => substs.as_generator().parent_substs(), DefKind::InlineConst => substs.as_inline_const().parent_substs(), other => bug!("unexpected item {:?}", other), }; let parent_substs = tcx.mk_substs(parent_substs.iter()); assert_eq!(typeck_root_substs.len(), parent_substs.len()); if let Err(_) = self.eq_substs( typeck_root_substs, parent_substs, location.to_locations(), ConstraintCategory::BoringNoLocation, ) { span_mirbug!( self, def_id, "could not relate closure to parent {:?} != {:?}", typeck_root_substs, parent_substs ); } </ins> tcx.predicates_of(def_id).instantiate(tcx, substs) }

.relate(a, b)?; Ok(()) } <ins> /// Add sufficient constraints to ensure `a == b`. See also [Self::relate_types]. pub(super) fn eq_substs( &mut self, a: ty::SubstsRef<'tcx>, b: ty::SubstsRef<'tcx>, locations: Locations, category: ConstraintCategory<'tcx>, ) -> Fallible<()> { TypeRelating::new( self.infcx, NllTypeRelatingDelegate::new(self, locations, category, UniverseInfo::other()), ty::Variance::Invariant, ) .relate(a, b)?; Ok(()) } </ins> } struct NllTypeRelatingDelegate<'me, 'bccx, 'tcx> {

pub(crate) cx: &'a mut core::DocContext<'tcx>, } <del> impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { </del> <ins> impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> where 'tcx: 'a, // should be an implied bound; rustc bug #98852. { </ins> pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> Self { AutoTraitFinder { cx } }

<ins> // Regression test for #98589. // Previously, named lifetime `'a` that appears in the closure was unrelated to `'a` // that appears in the parent function iff `'a` is early-bound. // This made the following tests pass borrowck. // check-fail // The bound `'a: 'a` ensures that `'a` is early-bound. fn test_early_early<'a: 'a, 'b: 'b>() { || { None::<&'a &'b ()>; }; //~^ ERROR lifetime may not live long enough } fn test_early_late<'a: 'a, 'b>() { || { None::<&'a &'b ()>; }; //~^ ERROR lifetime may not live long enough } // No early-bound lifetime; included for completeness. fn test_late_late<'a, 'b>() { || { None::<&'a &'b ()>; }; //~^ ERROR lifetime may not live long enough } fn test_early_type<'a: 'a, T>() { || { None::<&'a T>; }; //~^ ERROR the parameter type `T` may not live long enough } // No early-bound lifetime; included for completeness. fn test_late_type<'a, T>() { || { None::<&'a T>; }; //~^ ERROR the parameter type `T` may not live long enough } fn main() {} </ins>

<ins> error: lifetime may not live long enough --> $DIR/issue-98589-closures-relate-named-regions.rs:10:5 | LL | fn test_early_early<'a: 'a, 'b: 'b>() { |                     --      -- lifetime `'b` defined here |                     | |                     lifetime `'a` defined here LL |     || { None::<&'a &'b ()>; }; |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'b` must outlive `'a` | = help: consider adding the following bound: `'b: 'a` error: lifetime may not live long enough --> $DIR/issue-98589-closures-relate-named-regions.rs:15:10 | LL | fn test_early_late<'a: 'a, 'b>() { |                    --      -- lifetime `'b` defined here |                    | |                    lifetime `'a` defined here LL |     || { None::<&'a &'b ()>; }; |          ^^^^^^^^^^^^^^^^^^ requires that `'b` must outlive `'a` | = help: consider adding the following bound: `'b: 'a` error: lifetime may not live long enough --> $DIR/issue-98589-closures-relate-named-regions.rs:21:10 | LL | fn test_late_late<'a, 'b>() { |                   --  -- lifetime `'b` defined here |                   | |                   lifetime `'a` defined here LL |     || { None::<&'a &'b ()>; }; |          ^^^^^^^^^^^^^^^^^^ requires that `'b` must outlive `'a` | = help: consider adding the following bound: `'b: 'a` error[E0309]: the parameter type `T` may not live long enough --> $DIR/issue-98589-closures-relate-named-regions.rs:26:5 | LL |     || { None::<&'a T>; }; |     ^^^^^^^^^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds | help: consider adding an explicit lifetime bound... | LL | fn test_early_type<'a: 'a, T: 'a>() { |                             ++++ error[E0309]: the parameter type `T` may not live long enough --> $DIR/issue-98589-closures-relate-named-regions.rs:32:5 | LL |     || { None::<&'a T>; }; |     ^^^^^^^^^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds | help: consider adding an explicit lifetime bound... | LL | fn test_late_type<'a, T: 'a>() { |                        ++++ error: aborting due to 5 previous errors For more information about this error, try `rustc --explain E0309`. </ins>