Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 218 additions & 1 deletion compiler/rustc_borrowck/src/diagnostics/region_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use rustc_middle::mir::{AnnotationSource, ConstraintCategory, ReturnConstraint};
use rustc_middle::ty::{
self, GenericArgs, Region, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitor, fold_regions,
};
use rustc_span::{Ident, Span, Symbol, bug, kw};
use rustc_span::{Ident, Pos, Span, Symbol, bug, kw};
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
use rustc_trait_selection::error_reporting::infer::nice_region_error::{
self, HirTraitObjectVisitor, NiceRegionError, TraitObjectVisitor, find_anon_type,
Expand Down Expand Up @@ -672,6 +672,7 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> {
if let ConstraintCategory::CallArgument(Some(ty)) = category {
self.explain_impl_static_obligation(&mut diag, ty, outlived_fr);
}
self.suggest_explicit_static_object_lifetime(&mut diag, &errci);

match variance_info {
ty::VarianceDiagInfo::None => {}
Expand Down Expand Up @@ -1254,6 +1255,222 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> {
}
}

fn suggest_explicit_static_object_lifetime(
&self,
diag: &mut Diag<'_>,
errci: &ErrorConstraintInfo<'tcx>,
) {
let tcx = self.infcx.tcx;
let (Some(region), Some(outlived_region)) = (
self.regioncx.to_error_region(errci.fr),
self.regioncx.to_error_region(errci.outlived_fr),
) else {
return;
};
if !outlived_region.is_static() {
return;
}
let ConstraintCategory::CallArgument(Some(fn_ty)) = errci.category else { return };
let ty::FnDef(def_id, args) = *fn_ty.kind() else { return };
let Some(args) = args.no_bound_vars() else { return };
if tcx.is_closure_like(def_id) {
return;
}
// CallArgument erases lifetime arguments. Bounds can still require the reference to
// outlive another region, even if the instantiated signature contains no `&'static`.
let clauses = tcx.clauses_of(def_id);
if clauses.instantiate_identity(tcx).into_iter().chain(clauses.instantiate(tcx, args)).any(
|(clause, _)| match clause.skip_norm_wip().kind().skip_binder() {
ty::ClauseKind::RegionOutlives(ty::OutlivesClause(longer, shorter)) => {
longer != shorter && !longer.is_static()
}
ty::ClauseKind::TypeOutlives(ty::OutlivesClause(ty, lifetime))
if lifetime.is_static() =>
{
ty.walk().any(|arg| {
matches!(arg.as_type().map(Ty::kind),
Some(ty::Ref(region, ..)) if !region.is_static())
})
}
_ => false,
},
) {
return;
}
let Some(call) = self.find_expr(errci.span) else { return };
let call_args: Vec<_> = match call.kind {
hir::ExprKind::Call(_, args) => args.iter().collect(),
hir::ExprKind::MethodCall(_, receiver, args, _) => {
std::iter::once(receiver).chain(args).collect()
}
_ => return,
};
let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) else { return };
let Some(hir_sig) = tcx.hir_fn_sig_by_hir_id(self.mir_hir_id()) else { return };
let decl = hir_sig.decl;
let callee_sig = tcx.fn_sig(def_id).instantiate(tcx, args).skip_norm_wip().skip_binder();
// Typeck results have erased regions. The liberated signature retains the object lifetime.
let caller_sig = tcx.liberate_late_bound_regions(
self.mir_def_id().to_def_id(),
tcx.fn_sig(self.mir_def_id()).instantiate_identity().skip_norm_wip(),
);
let needs_static_reference = |mut ty: Ty<'tcx>| {
while let ty::Ref(lifetime, inner, _) = *ty.kind() {
if lifetime.is_static() {
return true;
}
ty = inner;
}
false
};
let mut seen_params = FxIndexSet::default();
for (arg, expected) in std::iter::zip(call_args.iter().copied(), callee_sig.inputs()) {
if needs_static_reference(*expected) {
continue;
}
let ty::Dynamic(expected_predicates, expected_region) = *expected.peel_refs().kind()
else {
continue;
};
if !expected_region.is_static() {
continue;
}

// Follow the actual argument back to its parameter, rather than editing another
// parameter that happens to use the same lifetime or trait.
let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = arg.kind else {
continue;
};
let hir::def::Res::Local(binding) = path.res else { continue };
let Some(index) = body.params.iter().position(|param| param.pat.hir_id == binding)
else {
continue;
};
let ty::Dynamic(predicates, object_region) =
*caller_sig.inputs()[index].peel_refs().kind()
else {
continue;
};
if object_region != region {
continue;
}
// Callee lifetimes may be bound or erased here. Preserve explicit `'static` and
// repeated lifetime arguments: changing the object bound cannot fix those mismatches.
let comparable_predicates = |predicates| {
let mut regions = FxIndexSet::default();
fold_regions(tcx, predicates, |region, _| {
if region.is_static() {
region
} else {
let (index, _) = regions.insert_full(region);
ty::Region::new_late_param(
tcx,
self.mir_def_id().to_def_id(),
ty::LateParamRegionKind::Anon(index as u32),
)
}
})
};
let source_predicates = comparable_predicates(predicates);
let target_predicates = comparable_predicates(expected_predicates);
if source_predicates.principal().map(|p| tcx.anonymize_bound_vars(p))
!= target_predicates.principal().map(|p| tcx.anonymize_bound_vars(p))
|| !source_predicates
.projection_bounds()
.map(|p| tcx.anonymize_bound_vars(p))
.eq(target_predicates.projection_bounds().map(|p| tcx.anonymize_bound_vars(p)))
{
continue;
}
// The same parameter may also be passed where the reference itself must be static.
if !seen_params.insert(index)
|| std::iter::zip(&call_args, callee_sig.inputs()).any(|(other, expected)| {
needs_static_reference(*expected)
&& matches!(other.kind,
hir::ExprKind::Path(hir::QPath::Resolved(None, path))
if path.res == hir::def::Res::Local(binding))
})
{
continue;
}
let param_ty = decl.inputs[index];
let object_ty = param_ty.peel_refs();
let hir::TyKind::TraitObject(_, lifetime) = object_ty.kind else { continue };
if lifetime.kind != hir::LifetimeKind::ImplicitObjectLifetimeDefault
|| !object_ty.span.can_be_used_for_suggestions()
{
continue;
}
let lifetime_explanation =
if rustc_trait_selection::traits::wf::object_region_bounds(tcx, predicates)
.is_empty()
{
"this trait object's lifetime defaults to the reference's lifetime"
} else {
"this trait object's lifetime is inferred from its trait's lifetime bounds"
};

struct ElidedOutputLifetime<'tcx> {
tcx: TyCtxt<'tcx>,
object_lifetime: &'tcx hir::Lifetime,
found: bool,
}
impl<'tcx> Visitor<'tcx> for ElidedOutputLifetime<'tcx> {
fn visit_lifetime(&mut self, lifetime: &'tcx hir::Lifetime) {
let resolved = self.tcx.named_bound_var(lifetime.hir_id);
self.found |= lifetime.syntax != hir::LifetimeSyntax::ExplicitBound
&& resolved.is_some()
&& resolved == self.tcx.named_bound_var(self.object_lifetime.hir_id);
}
}
let mut output_lifetime =
ElidedOutputLifetime { tcx, object_lifetime: lifetime.pointer(), found: false };
if let hir::FnRetTy::Return(output) = decl.output {
output_lifetime.visit_ty_unambig(output);
}
if output_lifetime.found {
// Adding another input lifetime would make output lifetime elision ambiguous.
diag.span_note(object_ty.span, lifetime_explanation);
continue;
}

let source_map = tcx.sess.source_map();
let Ok(mut snippet) = source_map.span_to_snippet(object_ty.span) else { continue };
if let hir::TyKind::TraitObject(bounds, _) = object_ty.kind
&& let Some(bound) = bounds.last()
&& let Some(segment) = bound.trait_ref.path.segments.last()
&& let Some(output) = segment.args().paren_sugar_output()
&& let output = output.peel_refs()
&& let hir::TyKind::TraitObject(..) = output.kind
{
if !object_ty.span.contains(output.span) {
continue;
}
// Otherwise `+ 'static` would bind to the callable's output trait object.
let start = (output.span.lo() - object_ty.span.lo()).to_usize();
let end = (output.span.hi() - object_ty.span.lo()).to_usize();
snippet.insert(end, ')');
snippet.insert(start, '(');
}
let prefix = source_map
.span_to_snippet(param_ty.span.with_hi(object_ty.span.lo()))
.unwrap_or_default();
let replacement = if prefix.trim_end().ends_with('(') {
format!("{snippet} + 'static")
} else {
format!("({snippet} + 'static)")
};
diag.span_suggestion(
object_ty.span,
format!(
"{lifetime_explanation}; consider adding an explicit `'static` bound to the trait object"
),
replacement,
Applicability::MaybeIncorrect,
);
}
}

fn maybe_suggest_constrain_dyn_trait_impl(
&self,
diag: &mut Diag<'_>,
Expand Down
121 changes: 121 additions & 0 deletions tests/ui/object-lifetime/implicit-object-lifetime-no-suggestion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#![allow(dead_code, mismatched_lifetime_syntaxes)]

use std::error::Error;

fn explicit_bound<'a, T: Error + 'static>(err: &(dyn Error + 'a)) -> bool {
err.is::<T>() //~ ERROR borrowed data escapes outside of function
}

fn explicit_placeholder<T: Error + 'static>(err: &(dyn Error + '_)) -> bool {
err.is::<T>() //~ ERROR borrowed data escapes outside of function
}

fn return_static(err: &dyn Error) -> &'static dyn Error {
err //~ ERROR lifetime may not live long enough
}

fn return_other<'a, 'b>(err: &'a dyn Error) -> &'b dyn Error {
err //~ ERROR lifetime may not live long enough
}

fn takes_static_reference(_: &'static dyn Error) {}

fn static_reference_argument(err: &dyn Error) {
takes_static_reference(err); //~ ERROR borrowed data escapes outside of function
}

fn takes_static_object_and_reference(_: &'static (dyn Error + 'static)) {}

fn static_reference_and_object(err: &dyn Error) {
takes_static_object_and_reference(err); //~ ERROR borrowed data escapes outside of function
}

fn takes_object_and_static_reference(_: &(dyn Error + 'static), _: &'static dyn Error) {}

fn repeated_static_reference(err: &dyn Error) {
takes_object_and_static_reference(err, err); //~ ERROR borrowed data escapes outside of function
}

fn takes_object_and_reference<'a>(_: &(dyn Error + 'a), _: &'static u32) {}

fn unrelated_parameter<'a>(err: &'a dyn Error, other: &'a u32) {
takes_object_and_reference(err, other); //~ ERROR borrowed data escapes outside of function
}

fn takes_generic_static<T: ?Sized + 'static>(_: &T) {}

fn generic_bound(err: &dyn Error) {
takes_generic_static(err); //~ ERROR borrowed data escapes outside of function
}

trait Trait {
fn needs_static(&self) where Self: 'static {}
}

fn where_self(err: &dyn Trait) {
err.needs_static(); //~ ERROR borrowed data escapes outside of function
}

fn elided_output<T: Error + 'static>(err: &dyn Error) -> Option<&T> {
err.downcast_ref::<T>() //~ ERROR borrowed data escapes outside of function
}

fn named_input_elided_output<'a, T: Error + 'static>(err: &'a dyn Error) -> Option<&T> {
err.downcast_ref::<T>() //~ ERROR borrowed data escapes outside of function
}

fn placeholder_output<T: Error + 'static>(err: &dyn Error) -> Option<&'_ T> {
err.downcast_ref::<T>() //~ ERROR borrowed data escapes outside of function
}

fn takes_predicate_reference<'a: 'static>(_: &'a (dyn Error + 'static)) {}

fn predicate_reference(err: &dyn Error) {
takes_predicate_reference(err); //~ ERROR borrowed data escapes outside of function
}

fn takes_transitive_reference<'a: 'b, 'b: 'static>(_: &'a (dyn Error + 'static)) {}

fn transitive_reference(err: &dyn Error) {
takes_transitive_reference(err); //~ ERROR borrowed data escapes outside of function
}

fn takes_reference_type<'a>(_: &'a (dyn Error + 'static))
where
&'a (dyn Error + 'static): 'static,
{
}

fn reference_type_bound(err: &dyn Error) {
takes_reference_type(err); //~ ERROR borrowed data escapes outside of function
}

trait LifetimeArgument<'a> {}

fn takes_static_argument(_: &(dyn LifetimeArgument<'static> + 'static)) {}

fn mismatched_argument<'a>(arg: &'a dyn LifetimeArgument<'a>) {
takes_static_argument(arg); //~ ERROR borrowed data escapes outside of function
}

trait AssociatedType {
type Item;
}

fn takes_static_associated_type(_: &(dyn AssociatedType<Item = &'static u32> + 'static)) {}

fn mismatched_associated_type<'a>(arg: &'a dyn AssociatedType<Item = &'a u32>) {
takes_static_associated_type(arg); //~ ERROR borrowed data escapes outside of function
}

trait RepeatedLifetime<'a, 'b> {}

fn takes_repeated_lifetime<'c>(_: &(dyn RepeatedLifetime<'c, 'c> + 'static)) {}

fn mismatched_repeated_lifetime<'a, 'b>(arg: &'a dyn RepeatedLifetime<'a, 'b>) {
//~^ ERROR one or more lifetime errors were found in this item
takes_repeated_lifetime(arg); //~ ERROR borrowed data escapes outside of function
//~^ ERROR borrowed data escapes outside of function
}

fn main() {}
Loading
Loading