suggest constraining dyn trait in impl in NLL

This commit is contained in:
b-naber 2022-05-22 17:04:49 +02:00
parent c1072d2a9a
commit 5f65331b3e
4 changed files with 208 additions and 28 deletions

View File

@ -161,7 +161,7 @@ impl OutlivesSuggestionBuilder {
pub(crate) fn intermediate_suggestion( pub(crate) fn intermediate_suggestion(
&mut self, &mut self,
mbcx: &MirBorrowckCtxt<'_, '_>, mbcx: &MirBorrowckCtxt<'_, '_>,
errci: &ErrorConstraintInfo, errci: &ErrorConstraintInfo<'_>,
diag: &mut Diagnostic, diag: &mut Diagnostic,
) { ) {
// Emit an intermediate note. // Emit an intermediate note.

View File

@ -1,10 +1,14 @@
//! Error reporting machinery for lifetime errors. //! Error reporting machinery for lifetime errors.
use rustc_errors::{Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; use rustc_data_structures::stable_set::FxHashSet;
use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan};
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::Visitor;
use rustc_hir::{self as hir, Item, ItemKind, Node};
use rustc_infer::infer::{ use rustc_infer::infer::{
error_reporting::nice_region_error::{ error_reporting::nice_region_error::{
self, find_anon_type, find_param_with_region, suggest_adding_lifetime_params, self, find_anon_type, find_param_with_region, suggest_adding_lifetime_params,
NiceRegionError, HirTraitObjectVisitor, NiceRegionError, TraitObjectVisitor,
}, },
error_reporting::unexpected_hidden_region_diagnostic, error_reporting::unexpected_hidden_region_diagnostic,
NllRegionVariableOrigin, RelateParamBound, NllRegionVariableOrigin, RelateParamBound,
@ -12,8 +16,11 @@ use rustc_infer::infer::{
use rustc_middle::hir::place::PlaceBase; use rustc_middle::hir::place::PlaceBase;
use rustc_middle::mir::{ConstraintCategory, ReturnConstraint}; use rustc_middle::mir::{ConstraintCategory, ReturnConstraint};
use rustc_middle::ty::subst::InternalSubsts; use rustc_middle::ty::subst::InternalSubsts;
use rustc_middle::ty::Region;
use rustc_middle::ty::TypeVisitor;
use rustc_middle::ty::{self, RegionVid, Ty}; use rustc_middle::ty::{self, RegionVid, Ty};
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
use rustc_span::symbol::Ident;
use rustc_span::Span; use rustc_span::Span;
use crate::borrowck_errors; use crate::borrowck_errors;
@ -27,7 +34,7 @@ use crate::{
MirBorrowckCtxt, MirBorrowckCtxt,
}; };
impl ConstraintDescription for ConstraintCategory { impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> {
fn description(&self) -> &'static str { fn description(&self) -> &'static str {
// Must end with a space. Allows for empty names to be provided. // Must end with a space. Allows for empty names to be provided.
match self { match self {
@ -37,7 +44,7 @@ impl ConstraintDescription for ConstraintCategory {
ConstraintCategory::UseAsConst => "using this value as a constant ", ConstraintCategory::UseAsConst => "using this value as a constant ",
ConstraintCategory::UseAsStatic => "using this value as a static ", ConstraintCategory::UseAsStatic => "using this value as a static ",
ConstraintCategory::Cast => "cast ", ConstraintCategory::Cast => "cast ",
ConstraintCategory::CallArgument => "argument ", ConstraintCategory::CallArgument(_) => "argument ",
ConstraintCategory::TypeAnnotation => "type annotation ", ConstraintCategory::TypeAnnotation => "type annotation ",
ConstraintCategory::ClosureBounds => "closure body ", ConstraintCategory::ClosureBounds => "closure body ",
ConstraintCategory::SizedBound => "proving this value is `Sized` ", ConstraintCategory::SizedBound => "proving this value is `Sized` ",
@ -101,7 +108,7 @@ pub(crate) enum RegionErrorKind<'tcx> {
/// Information about the various region constraints involved in a borrow checker error. /// Information about the various region constraints involved in a borrow checker error.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ErrorConstraintInfo { pub struct ErrorConstraintInfo<'tcx> {
// fr: outlived_fr // fr: outlived_fr
pub(super) fr: RegionVid, pub(super) fr: RegionVid,
pub(super) fr_is_local: bool, pub(super) fr_is_local: bool,
@ -109,7 +116,7 @@ pub struct ErrorConstraintInfo {
pub(super) outlived_fr_is_local: bool, pub(super) outlived_fr_is_local: bool,
// Category and span for best blame constraint // Category and span for best blame constraint
pub(super) category: ConstraintCategory, pub(super) category: ConstraintCategory<'tcx>,
pub(super) span: Span, pub(super) span: Span,
} }
@ -256,6 +263,70 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
outlives_suggestion.add_suggestion(self); outlives_suggestion.add_suggestion(self);
} }
fn get_impl_ident_and_self_ty_from_trait(
&self,
def_id: DefId,
trait_objects: &FxHashSet<DefId>,
) -> Option<(Ident, &'tcx hir::Ty<'tcx>)> {
let tcx = self.infcx.tcx;
match tcx.hir().get_if_local(def_id) {
Some(Node::ImplItem(impl_item)) => {
match tcx.hir().find_by_def_id(tcx.hir().get_parent_item(impl_item.hir_id())) {
Some(Node::Item(Item {
kind: ItemKind::Impl(hir::Impl { self_ty, .. }),
..
})) => Some((impl_item.ident, self_ty)),
_ => None,
}
}
Some(Node::TraitItem(trait_item)) => {
let trait_did = tcx.hir().get_parent_item(trait_item.hir_id());
match tcx.hir().find_by_def_id(trait_did) {
Some(Node::Item(Item { kind: ItemKind::Trait(..), .. })) => {
// The method being called is defined in the `trait`, but the `'static`
// obligation comes from the `impl`. Find that `impl` so that we can point
// at it in the suggestion.
let trait_did = trait_did.to_def_id();
match tcx
.hir()
.trait_impls(trait_did)
.iter()
.filter_map(|&impl_did| {
match tcx.hir().get_if_local(impl_did.to_def_id()) {
Some(Node::Item(Item {
kind: ItemKind::Impl(hir::Impl { self_ty, .. }),
..
})) if trait_objects.iter().all(|did| {
// FIXME: we should check `self_ty` against the receiver
// type in the `UnifyReceiver` context, but for now, use
// this imperfect proxy. This will fail if there are
// multiple `impl`s for the same trait like
// `impl Foo for Box<dyn Bar>` and `impl Foo for dyn Bar`.
// In that case, only the first one will get suggestions.
let mut traits = vec![];
let mut hir_v = HirTraitObjectVisitor(&mut traits, *did);
hir_v.visit_ty(self_ty);
!traits.is_empty()
}) =>
{
Some(self_ty)
}
_ => None,
}
})
.next()
{
Some(self_ty) => Some((trait_item.ident, self_ty)),
_ => None,
}
}
_ => None,
}
}
_ => None,
}
}
/// Report an error because the universal region `fr` was required to outlive /// Report an error because the universal region `fr` was required to outlive
/// `outlived_fr` but it is not known to do so. For example: /// `outlived_fr` but it is not known to do so. For example:
/// ///
@ -279,6 +350,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
}); });
debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info); debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info);
// Check if we can use one of the "nice region errors". // Check if we can use one of the "nice region errors".
if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) { if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) {
let nice = NiceRegionError::new_from_span(self.infcx, cause.span, o, f); let nice = NiceRegionError::new_from_span(self.infcx, cause.span, o, f);
@ -312,7 +384,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
self.report_fnmut_error(&errci, kind) self.report_fnmut_error(&errci, kind)
} }
(ConstraintCategory::Assignment, true, false) (ConstraintCategory::Assignment, true, false)
| (ConstraintCategory::CallArgument, true, false) => { | (ConstraintCategory::CallArgument(_), true, false) => {
let mut db = self.report_escaping_data_error(&errci); let mut db = self.report_escaping_data_error(&errci);
outlives_suggestion.intermediate_suggestion(self, &errci, &mut db); outlives_suggestion.intermediate_suggestion(self, &errci, &mut db);
@ -405,7 +477,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
/// ``` /// ```
fn report_fnmut_error( fn report_fnmut_error(
&self, &self,
errci: &ErrorConstraintInfo, errci: &ErrorConstraintInfo<'tcx>,
kind: ReturnConstraint, kind: ReturnConstraint,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
let ErrorConstraintInfo { outlived_fr, span, .. } = errci; let ErrorConstraintInfo { outlived_fr, span, .. } = errci;
@ -486,7 +558,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
/// ``` /// ```
fn report_escaping_data_error( fn report_escaping_data_error(
&self, &self,
errci: &ErrorConstraintInfo, errci: &ErrorConstraintInfo<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
let ErrorConstraintInfo { span, category, .. } = errci; let ErrorConstraintInfo { span, category, .. } = errci;
@ -548,24 +620,28 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
// Only show an extra note if we can find an 'error region' for both of the region // Only show an extra note if we can find an 'error region' for both of the region
// variables. This avoids showing a noisy note that just mentions 'synthetic' regions // variables. This avoids showing a noisy note that just mentions 'synthetic' regions
// that don't help the user understand the error. // that don't help the user understand the error.
if self.to_error_region(errci.fr).is_some() match (self.to_error_region(errci.fr), self.to_error_region(errci.outlived_fr)) {
&& self.to_error_region(errci.outlived_fr).is_some() (Some(f), Some(o)) => {
{ self.maybe_suggest_constrain_dyn_trait_impl(&mut diag, f, o, category);
let fr_region_name = self.give_region_a_name(errci.fr).unwrap();
fr_region_name.highlight_region_name(&mut diag);
let outlived_fr_region_name = self.give_region_a_name(errci.outlived_fr).unwrap();
outlived_fr_region_name.highlight_region_name(&mut diag);
diag.span_label( let fr_region_name = self.give_region_a_name(errci.fr).unwrap();
*span, fr_region_name.highlight_region_name(&mut diag);
format!( let outlived_fr_region_name = self.give_region_a_name(errci.outlived_fr).unwrap();
"{}requires that `{}` must outlive `{}`", outlived_fr_region_name.highlight_region_name(&mut diag);
category.description(),
fr_region_name, diag.span_label(
outlived_fr_region_name, *span,
), format!(
); "{}requires that `{}` must outlive `{}`",
category.description(),
fr_region_name,
outlived_fr_region_name,
),
);
}
_ => {}
} }
diag diag
} }
@ -586,7 +662,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
/// ``` /// ```
fn report_general_error( fn report_general_error(
&self, &self,
errci: &ErrorConstraintInfo, errci: &ErrorConstraintInfo<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
let ErrorConstraintInfo { let ErrorConstraintInfo {
fr, fr,
@ -699,6 +775,99 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
} }
} }
fn maybe_suggest_constrain_dyn_trait_impl(
&self,
diag: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
f: Region<'tcx>,
o: Region<'tcx>,
category: &ConstraintCategory<'tcx>,
) {
if !o.is_static() {
return;
}
let tcx = self.infcx.tcx;
let instance = if let ConstraintCategory::CallArgument(Some((fn_did, substs))) = category {
debug!(?fn_did, ?substs);
// Only suggest this on function calls, not closures
let ty = tcx.type_of(fn_did);
debug!("ty: {:?}, ty.kind: {:?}", ty, ty.kind());
if let ty::Closure(_, _) = ty.kind() {
return;
}
if let Ok(Some(instance)) = ty::Instance::resolve(
tcx,
self.param_env,
*fn_did,
self.infcx.resolve_vars_if_possible(substs),
) {
instance
} else {
return;
}
} else {
return;
};
let param = match find_param_with_region(tcx, f, o) {
Some(param) => param,
None => return,
};
debug!(?param);
let mut visitor = TraitObjectVisitor(FxHashSet::default());
visitor.visit_ty(param.param_ty);
if let Some((ident, self_ty)) =
self.get_impl_ident_and_self_ty_from_trait(instance.def_id(), &visitor.0)
{
self.suggest_constrain_dyn_trait_in_impl(diag, &visitor.0, ident, self_ty)
} else {
return;
};
}
#[instrument(skip(self, err), level = "debug")]
fn suggest_constrain_dyn_trait_in_impl(
&self,
err: &mut Diagnostic,
found_dids: &FxHashSet<DefId>,
ident: Ident,
self_ty: &hir::Ty<'_>,
) -> bool {
debug!("err: {:#?}", err);
let mut suggested = false;
for found_did in found_dids {
let mut traits = vec![];
let mut hir_v = HirTraitObjectVisitor(&mut traits, *found_did);
hir_v.visit_ty(&self_ty);
debug!("trait spans found: {:?}", traits);
for span in &traits {
let mut multi_span: MultiSpan = vec![*span].into();
multi_span.push_span_label(
*span,
"this has an implicit `'static` lifetime requirement".to_string(),
);
multi_span.push_span_label(
ident.span,
"calling this method introduces the `impl`'s 'static` requirement".to_string(),
);
err.span_note(multi_span, "the used `impl` has a `'static` requirement");
err.span_suggestion_verbose(
span.shrink_to_hi(),
"consider relaxing the implicit `'static` requirement",
" + '_".to_string(),
Applicability::MaybeIncorrect,
);
suggested = true;
}
}
suggested
}
fn suggest_adding_lifetime_params( fn suggest_adding_lifetime_params(
&self, &self,
diag: &mut Diagnostic, diag: &mut Diagnostic,

View File

@ -938,7 +938,7 @@ pub(crate) struct MirTypeckRegionConstraints<'tcx> {
pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>, pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>,
crate closure_bounds_mapping: pub(crate) closure_bounds_mapping:
FxHashMap<Location, FxHashMap<(RegionVid, RegionVid), (ConstraintCategory<'tcx>, Span)>>, FxHashMap<Location, FxHashMap<(RegionVid, RegionVid), (ConstraintCategory<'tcx>, Span)>>,
pub(crate) universe_causes: FxHashMap<ty::UniverseIndex, UniverseInfo<'tcx>>, pub(crate) universe_causes: FxHashMap<ty::UniverseIndex, UniverseInfo<'tcx>>,

View File

@ -34,6 +34,7 @@ pub struct AnonymousParamInfo<'tcx> {
// i32, which is the type of y but with the anonymous region replaced // i32, which is the type of y but with the anonymous region replaced
// with 'a, the corresponding bound region and is_first which is true if // with 'a, the corresponding bound region and is_first which is true if
// the hir::Param is the first parameter in the function declaration. // the hir::Param is the first parameter in the function declaration.
#[instrument(skip(tcx), level = "debug")]
pub fn find_param_with_region<'tcx>( pub fn find_param_with_region<'tcx>(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
anon_region: Region<'tcx>, anon_region: Region<'tcx>,
@ -51,9 +52,19 @@ pub fn find_param_with_region<'tcx>(
let hir_id = hir.local_def_id_to_hir_id(id.as_local()?); let hir_id = hir.local_def_id_to_hir_id(id.as_local()?);
let body_id = hir.maybe_body_owned_by(hir_id)?; let body_id = hir.maybe_body_owned_by(hir_id)?;
let body = hir.body(body_id); let body = hir.body(body_id);
// Don't perform this on closures
match hir.get(hir_id) {
hir::Node::Expr(&hir::Expr { kind: hir::ExprKind::Closure(..), .. }) => {
return None;
}
_ => {}
}
let owner_id = hir.body_owner(body_id); let owner_id = hir.body_owner(body_id);
let fn_decl = hir.fn_decl_by_hir_id(owner_id).unwrap(); let fn_decl = hir.fn_decl_by_hir_id(owner_id).unwrap();
let poly_fn_sig = tcx.fn_sig(id); let poly_fn_sig = tcx.fn_sig(id);
let fn_sig = tcx.liberate_late_bound_regions(id, poly_fn_sig); let fn_sig = tcx.liberate_late_bound_regions(id, poly_fn_sig);
body.params body.params
.iter() .iter()