Skip to content

Commit 8cd50f0

Browse files
committed
Report a specialized error when a 'static obligation comes from an impl dyn Trait
```text error: lifetime may not live long enough --> $DIR/static-impl-obligation.rs:8:27 | LL | fn bar<'a>(x: &'a &'a u32) { | -- lifetime `'a` defined here LL | let y: &dyn Foo = x; | ^ cast requires that `'a` must outlive `'static` LL | y.hello(); | --------- calling this method introduces a `'static` lifetime requirement | help: relax the implicit `'static` bound on the impl | LL | impl dyn Foo + '_ { | ++++ ``` ```text error: lifetime may not live long enough --> $DIR/static-impl-obligation.rs:173:27 | LL | fn bar<'a>(x: &'a &'a u32) { | -- lifetime `'a` defined here LL | let y: &dyn Foo = x; | ^ cast requires that `'a` must outlive `'static` LL | y.hello(); | --------- calling this method introduces a `'static` lifetime requirement | note: the `impl` on `(dyn p::Foo + 'static)` has `'static` lifetime requirements --> $DIR/static-impl-obligation.rs:169:20 | LL | impl dyn Foo + 'static where Self: 'static { | ^^^^^^^ ^^^^^^^ LL | fn hello(&self) where Self: 'static {} | ^^^^^^^ ```
1 parent 8a49772 commit 8cd50f0

File tree

18 files changed

+800
-53
lines changed

18 files changed

+800
-53
lines changed

compiler/rustc_borrowck/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2021"
88
either = "1.5.0"
99
itertools = "0.11"
1010
polonius-engine = "0.13.0"
11+
rustc_ast = { path = "../rustc_ast" }
1112
rustc_data_structures = { path = "../rustc_data_structures" }
1213
rustc_errors = { path = "../rustc_errors" }
1314
rustc_fluent_macro = { path = "../rustc_fluent_macro" }

compiler/rustc_borrowck/src/diagnostics/region_errors.rs

+232-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Error reporting machinery for lifetime errors.
22
3+
use rustc_ast::TraitObjectSyntax::Dyn;
34
use rustc_data_structures::fx::FxIndexSet;
45
use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, MultiSpan};
56
use rustc_hir as hir;
@@ -16,14 +17,12 @@ use rustc_infer::infer::{
1617
HirTraitObjectVisitor, NiceRegionError, TraitObjectVisitor,
1718
},
1819
error_reporting::unexpected_hidden_region_diagnostic,
19-
NllRegionVariableOrigin, RelateParamBound,
20+
BoundRegionConversionTime, NllRegionVariableOrigin, RelateParamBound,
2021
};
2122
use rustc_middle::hir::place::PlaceBase;
2223
use rustc_middle::mir::{ConstraintCategory, ReturnConstraint};
23-
use rustc_middle::ty::GenericArgs;
24-
use rustc_middle::ty::TypeVisitor;
25-
use rustc_middle::ty::{self, RegionVid, Ty};
26-
use rustc_middle::ty::{Region, TyCtxt};
24+
use rustc_middle::traits::ObligationCauseCode;
25+
use rustc_middle::ty::{self, GenericArgs, Region, RegionVid, Ty, TyCtxt, TypeVisitor};
2726
use rustc_span::symbol::{kw, Ident};
2827
use rustc_span::Span;
2928

@@ -490,19 +489,21 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
490489
}
491490
};
492491

492+
self.explain_impl_static_obligation(&mut diag, cause.code(), outlived_fr);
493+
493494
match variance_info {
494495
ty::VarianceDiagInfo::None => {}
495496
ty::VarianceDiagInfo::Invariant { ty, param_index } => {
496497
let (desc, note) = match ty.kind() {
497498
ty::RawPtr(ty_mut) => {
498-
assert_eq!(ty_mut.mutbl, rustc_hir::Mutability::Mut);
499+
assert_eq!(ty_mut.mutbl, hir::Mutability::Mut);
499500
(
500501
format!("a mutable pointer to `{}`", ty_mut.ty),
501502
"mutable pointers are invariant over their type parameter".to_string(),
502503
)
503504
}
504505
ty::Ref(_, inner_ty, mutbl) => {
505-
assert_eq!(*mutbl, rustc_hir::Mutability::Mut);
506+
assert_eq!(*mutbl, hir::Mutability::Mut);
506507
(
507508
format!("a mutable reference to `{inner_ty}`"),
508509
"mutable references are invariant over their type parameter"
@@ -518,10 +519,12 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
518519
let adt_desc = adt.descr();
519520

520521
let desc = format!(
521-
"the type `{ty}`, which makes the generic argument `{generic_arg}` invariant"
522+
"the type `{ty}`, which makes the generic argument `{generic_arg}` \
523+
invariant"
522524
);
523525
let note = format!(
524-
"the {adt_desc} `{base_ty}` is invariant over the parameter `{base_generic_arg}`"
526+
"the {adt_desc} `{base_ty}` is invariant over the parameter \
527+
`{base_generic_arg}`"
525528
);
526529
(desc, note)
527530
}
@@ -539,21 +542,225 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
539542
};
540543
diag.note(format!("requirement occurs because of {desc}",));
541544
diag.note(note);
542-
diag.help("see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance");
545+
diag.help(
546+
"see <https://doc.rust-lang.org/nomicon/subtyping.html> for more \
547+
information about variance",
548+
);
543549
}
544550
}
545551

546552
for extra in extra_info {
547553
match extra {
548554
ExtraConstraintInfo::PlaceholderFromPredicate(span) => {
549-
diag.span_note(span, "due to current limitations in the borrow checker, this implies a `'static` lifetime");
555+
diag.span_note(
556+
span,
557+
"due to current limitations in the borrow checker, this implies a \
558+
`'static` lifetime",
559+
);
550560
}
551561
}
552562
}
553563

554564
self.buffer_error(diag);
555565
}
556566

567+
/// Report a specialized error when a `'static` obligation comes from an `impl dyn Trait`
568+
///
569+
/// ```text
570+
/// error: lifetime may not live long enough
571+
/// --> $DIR/static-impl-obligation.rs:8:27
572+
/// |
573+
/// LL | fn bar<'a>(x: &'a &'a u32) {
574+
/// | -- lifetime `'a` defined here
575+
/// LL | let y: &dyn Foo = x;
576+
/// | ^ cast requires that `'a` must outlive `'static`
577+
/// LL | y.hello();
578+
/// | --------- calling this method introduces a `'static` lifetime requirement
579+
/// |
580+
/// note: the `impl` on `(dyn a::Foo + 'static)` has a `'static` lifetime requirement
581+
/// --> $DIR/static-impl-obligation.rs:4:10
582+
/// |
583+
/// LL | impl dyn Foo {
584+
/// | ^^^^^^^
585+
/// help: relax the implicit `'static` bound on the impl
586+
/// |
587+
/// LL | impl dyn Foo + '_ {
588+
/// | ++++
589+
/// ```
590+
/// ```text
591+
/// error: lifetime may not live long enough
592+
/// --> $DIR/static-impl-obligation.rs:173:27
593+
/// |
594+
/// LL | fn bar<'a>(x: &'a &'a u32) {
595+
/// | -- lifetime `'a` defined here
596+
/// LL | let y: &dyn Foo = x;
597+
/// | ^ cast requires that `'a` must outlive `'static`
598+
/// LL | y.hello();
599+
/// | --------- calling this method introduces a `'static` lifetime requirement
600+
/// |
601+
/// note: the `impl` on `(dyn p::Foo + 'static)` has `'static` lifetime requirements
602+
/// --> $DIR/static-impl-obligation.rs:169:20
603+
/// |
604+
/// LL | impl dyn Foo + 'static where Self: 'static {
605+
/// | ^^^^^^^ ^^^^^^^
606+
/// LL | fn hello(&self) where Self: 'static {}
607+
/// | ^^^^^^^
608+
/// ```
609+
fn explain_impl_static_obligation(
610+
&self,
611+
diag: &mut DiagnosticBuilder<'_>,
612+
code: &ObligationCauseCode<'tcx>,
613+
outlived_fr: RegionVid,
614+
) {
615+
let tcx = self.infcx.tcx;
616+
let ObligationCauseCode::MethodCallConstraint(ty, call_span) = code else {
617+
return;
618+
};
619+
let ty::FnDef(def_id, args) = ty.kind() else {
620+
return;
621+
};
622+
let parent = tcx.parent(*def_id);
623+
let hir::def::DefKind::Impl { .. } = tcx.def_kind(parent) else {
624+
return;
625+
};
626+
let ty = tcx.type_of(parent).instantiate(tcx, args);
627+
if self.to_error_region(outlived_fr) != Some(tcx.lifetimes.re_static) {
628+
return;
629+
}
630+
// FIXME: there's a case that's yet to be handled: `impl dyn Trait + '_ where Self: '_`
631+
// causes *two* errors to be produded, one about `where Self: '_` not being allowed,
632+
// and the regular error with no additional information about "lifetime may not live
633+
// long enough for `'static`" without mentioning where it came from. This is because
634+
// our error recovery fallback is indeed `ReStatic`. We should at some point introduce
635+
// a `ReError` instead to avoid this and other similar issues.
636+
637+
// Look for `'static` bounds in the generics of the method and the `impl`.
638+
// ```
639+
// impl dyn Trait where Self: 'static {
640+
// fn foo(&self) where Self: 'static {}
641+
// }
642+
// ```
643+
let mut predicates: Vec<Span> = tcx
644+
.predicates_of(*def_id)
645+
.predicates
646+
.iter()
647+
.chain(tcx.predicates_of(parent).predicates.iter())
648+
.filter_map(|(pred, pred_span)| {
649+
if let Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(pred_ty, r))) =
650+
pred.kind().no_bound_vars()
651+
// Look for `'static` bounds
652+
&& r.kind() == ty::ReStatic
653+
// We only want bounds on `Self`
654+
&& self.infcx.can_eq(self.param_env, ty, pred_ty)
655+
{
656+
Some(*pred_span)
657+
} else {
658+
None
659+
}
660+
})
661+
.collect();
662+
663+
// Look at the receiver for `&'static self`, which introduces a `'static` obligation.
664+
// ```
665+
// impl dyn Trait {
666+
// fn foo(&'static self) {}
667+
// }
668+
// ```
669+
if let ty::Ref(region, _, _) = self
670+
.infcx
671+
.instantiate_binder_with_fresh_vars(
672+
*call_span,
673+
BoundRegionConversionTime::FnCall,
674+
tcx.fn_sig(*def_id).instantiate_identity().inputs().map_bound(|inputs| inputs[0]),
675+
)
676+
.kind()
677+
&& *region == tcx.lifetimes.re_static
678+
&& let Some(assoc) = tcx.opt_associated_item(*def_id)
679+
&& assoc.fn_has_self_parameter
680+
{
681+
// We have a `&'static self` receiver.
682+
if let Some(def_id) = def_id.as_local()
683+
&& let owner = tcx.expect_hir_owner_node(def_id)
684+
&& let Some(decl) = owner.fn_decl()
685+
&& let Some(ty) = decl.inputs.get(0)
686+
{
687+
// Point at the `&'static self` receiver.
688+
predicates.push(ty.span);
689+
} else {
690+
// The method is not defined on the local crate, point at the signature
691+
// instead of just the receiver as an approximation.
692+
predicates.push(tcx.def_span(*def_id))
693+
}
694+
}
695+
696+
// When we have the HIR `Node` at hand, see if we can identify an
697+
// implicit `'static` bound in an `impl dyn Trait {}` and if that's
698+
// the only restriction, suggest relaxing it.
699+
if let Some(hir::Node::Item(hir::Item {
700+
kind:
701+
hir::ItemKind::Impl(hir::Impl {
702+
self_ty: hir::Ty { kind: hir::TyKind::TraitObject(_, lt, _), span, .. },
703+
..
704+
}),
705+
..
706+
})) = tcx.hir().get_if_local(parent)
707+
&& let Some(hir::Node::ImplItem(hir::ImplItem { .. })) = tcx.hir().get_if_local(*def_id)
708+
{
709+
let suggestion = match lt.res {
710+
hir::LifetimeName::ImplicitObjectLifetimeDefault if predicates.is_empty() => {
711+
// `impl dyn Trait {}`
712+
Some((
713+
span.shrink_to_hi(),
714+
"consider relaxing the implicit `'static` requirement on the impl",
715+
" + '_",
716+
))
717+
}
718+
hir::LifetimeName::Static if predicates.is_empty() => {
719+
// `impl dyn Trait + 'static {}`
720+
Some((lt.ident.span, "consider replacing this `'static` requirement", "'_"))
721+
}
722+
_ => None,
723+
};
724+
if let Some((span, msg, sugg)) = suggestion {
725+
// We only emit the suggestion to write `impl dyn Trait + '_ {}` if that's the only
726+
// thing needed.
727+
diag.span_suggestion_verbose(span, msg, sugg, Applicability::MachineApplicable);
728+
// This is redundant but needed because we won't enter the section with the
729+
// additional note, so we point at the method call here too.
730+
diag.span_label(
731+
*call_span,
732+
"calling this method introduces a `'static` lifetime requirement",
733+
);
734+
} else if let hir::LifetimeName::ImplicitObjectLifetimeDefault
735+
| hir::LifetimeName::Static = lt.res
736+
{
737+
// Otherwise, we add the right span for the note pointing at all the places where
738+
// a `'static` requirement is introduced when invoking the method on this `impl`.
739+
predicates.push(lt.ident.span);
740+
}
741+
} else if let ty::Dynamic(_, region, ty::Dyn) = ty.kind()
742+
&& *region == tcx.lifetimes.re_static
743+
{
744+
// The `self_ty` has a `'static` bound on a `dyn Trait`, either implicit or explicit,
745+
// but we don't have access to the HIR to identify which one nor to provide a targetted
746+
// enough `Span`, so instead we fall back to pointing at the `impl` header instead.
747+
predicates.push(tcx.def_span(parent));
748+
}
749+
if !predicates.is_empty() {
750+
diag.span_label(
751+
*call_span,
752+
"calling this method introduces a `'static` lifetime requirement",
753+
);
754+
let a_static_lt = if predicates.len() == 1 {
755+
"a `'static` lifetime requirement"
756+
} else {
757+
"`'static` lifetime requirements"
758+
};
759+
let span: MultiSpan = predicates.into();
760+
diag.span_note(span, format!("the `impl` on `{ty}` has {a_static_lt}"));
761+
}
762+
}
763+
557764
/// Report a specialized error when `FnMut` closures return a reference to a captured variable.
558765
/// This function expects `fr` to be local and `outlived_fr` to not be local.
559766
///
@@ -793,7 +1000,6 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
7931000
self.add_static_impl_trait_suggestion(&mut diag, *fr, fr_name, *outlived_fr);
7941001
self.suggest_adding_lifetime_params(&mut diag, *fr, *outlived_fr);
7951002
self.suggest_move_on_borrowing_closure(&mut diag);
796-
7971003
diag
7981004
}
7991005

@@ -980,12 +1186,20 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
9801186
"calling this method introduces the `impl`'s `'static` requirement",
9811187
);
9821188
err.subdiagnostic(self.dcx(), RequireStaticErr::UsedImpl { multi_span });
983-
err.span_suggestion_verbose(
984-
span.shrink_to_hi(),
985-
"consider relaxing the implicit `'static` requirement",
986-
" + '_",
987-
Applicability::MaybeIncorrect,
988-
);
1189+
if let hir::TyKind::TraitObject(traits, lt, Dyn) = self_ty.kind
1190+
&& lt.res == hir::LifetimeName::ImplicitObjectLifetimeDefault
1191+
&& traits.iter().any(|t| t.span == *span)
1192+
{
1193+
// We already handle the case where `self_ty` has an implicit 'static`
1194+
// requirement specifically in `explain_impl_static_obligation`.
1195+
} else {
1196+
err.span_suggestion_verbose(
1197+
span.shrink_to_hi(),
1198+
"consider relaxing the implicit `'static` requirement",
1199+
" + '_",
1200+
Applicability::MaybeIncorrect,
1201+
);
1202+
}
9891203
suggested = true;
9901204
}
9911205
}

compiler/rustc_borrowck/src/region_infer/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2051,6 +2051,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
20512051
CRATE_DEF_ID.to_def_id(),
20522052
predicate_span,
20532053
))
2054+
} else if let ConstraintCategory::CallArgument(Some(fn_def)) = constraint.category {
2055+
Some(ObligationCauseCode::MethodCallConstraint(fn_def, constraint.span))
20542056
} else {
20552057
None
20562058
}

compiler/rustc_borrowck/src/type_check/canonical.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
100100
locations: Locations,
101101
) {
102102
for (predicate, span) in instantiated_predicates {
103-
debug!(?predicate);
103+
debug!(?span, ?predicate);
104104
let category = ConstraintCategory::Predicate(span);
105105
let predicate = self.normalize_with_category(predicate, locations, category);
106106
self.prove_predicate(predicate, locations, category);

compiler/rustc_borrowck/src/type_check/constraint_conversion.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_middle::mir::{ClosureOutlivesSubject, ClosureRegionRequirements, Const
88
use rustc_middle::traits::query::NoSolution;
99
use rustc_middle::traits::ObligationCause;
1010
use rustc_middle::ty::{self, GenericArgKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt};
11-
use rustc_span::{Span, DUMMY_SP};
11+
use rustc_span::Span;
1212
use rustc_trait_selection::solve::deeply_normalize;
1313
use rustc_trait_selection::traits::query::type_op::custom::CustomTypeOp;
1414
use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
@@ -183,7 +183,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
183183

184184
// we don't actually use this for anything, but
185185
// the `TypeOutlives` code needs an origin.
186-
let origin = infer::RelateParamBound(DUMMY_SP, t1, None);
186+
let origin = infer::RelateParamBound(self.span, t1, None);
187187

188188
TypeOutlives::new(
189189
&mut *self,

0 commit comments

Comments
 (0)