summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs')
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs234
1 files changed, 234 insertions, 0 deletions
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs
new file mode 100644
index 000000000..9a2ab3e32
--- /dev/null
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs
@@ -0,0 +1,234 @@
+//! Error Reporting for Anonymous Region Lifetime Errors
+//! where both the regions are anonymous.
+
+use crate::infer::error_reporting::nice_region_error::find_anon_type::find_anon_type;
+use crate::infer::error_reporting::nice_region_error::util::AnonymousParamInfo;
+use crate::infer::error_reporting::nice_region_error::NiceRegionError;
+use crate::infer::lexical_region_resolve::RegionResolutionError;
+use crate::infer::SubregionOrigin;
+use crate::infer::TyCtxt;
+
+use rustc_errors::{struct_span_err, Applicability, Diagnostic, ErrorGuaranteed};
+use rustc_hir as hir;
+use rustc_hir::{GenericParamKind, Ty};
+use rustc_middle::ty::Region;
+use rustc_span::symbol::kw;
+
+impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
+ /// Print the error message for lifetime errors when both the concerned regions are anonymous.
+ ///
+ /// Consider a case where we have
+ ///
+ /// ```compile_fail,E0623
+ /// fn foo(x: &mut Vec<&u8>, y: &u8) {
+ /// x.push(y);
+ /// }
+ /// ```
+ ///
+ /// The example gives
+ ///
+ /// ```text
+ /// fn foo(x: &mut Vec<&u8>, y: &u8) {
+ /// --- --- these references are declared with different lifetimes...
+ /// x.push(y);
+ /// ^ ...but data from `y` flows into `x` here
+ /// ```
+ ///
+ /// It has been extended for the case of structs too.
+ ///
+ /// Consider the example
+ ///
+ /// ```no_run
+ /// struct Ref<'a> { x: &'a u32 }
+ /// ```
+ ///
+ /// ```text
+ /// fn foo(mut x: Vec<Ref>, y: Ref) {
+ /// --- --- these structs are declared with different lifetimes...
+ /// x.push(y);
+ /// ^ ...but data from `y` flows into `x` here
+ /// }
+ /// ```
+ ///
+ /// It will later be extended to trait objects.
+ pub(super) fn try_report_anon_anon_conflict(&self) -> Option<ErrorGuaranteed> {
+ let (span, sub, sup) = self.regions()?;
+
+ if let Some(RegionResolutionError::ConcreteFailure(
+ SubregionOrigin::ReferenceOutlivesReferent(..),
+ ..,
+ )) = self.error
+ {
+ // This error doesn't make much sense in this case.
+ return None;
+ }
+
+ // Determine whether the sub and sup consist of both anonymous (elided) regions.
+ let anon_reg_sup = self.tcx().is_suitable_region(sup)?;
+
+ let anon_reg_sub = self.tcx().is_suitable_region(sub)?;
+ let scope_def_id_sup = anon_reg_sup.def_id;
+ let bregion_sup = anon_reg_sup.boundregion;
+ let scope_def_id_sub = anon_reg_sub.def_id;
+ let bregion_sub = anon_reg_sub.boundregion;
+
+ let ty_sup = find_anon_type(self.tcx(), sup, &bregion_sup)?;
+
+ let ty_sub = find_anon_type(self.tcx(), sub, &bregion_sub)?;
+
+ debug!(
+ "try_report_anon_anon_conflict: found_param1={:?} sup={:?} br1={:?}",
+ ty_sub, sup, bregion_sup
+ );
+ debug!(
+ "try_report_anon_anon_conflict: found_param2={:?} sub={:?} br2={:?}",
+ ty_sup, sub, bregion_sub
+ );
+
+ let (ty_sup, ty_fndecl_sup) = ty_sup;
+ let (ty_sub, ty_fndecl_sub) = ty_sub;
+
+ let AnonymousParamInfo { param: anon_param_sup, .. } =
+ self.find_param_with_region(sup, sup)?;
+ let AnonymousParamInfo { param: anon_param_sub, .. } =
+ self.find_param_with_region(sub, sub)?;
+
+ let sup_is_ret_type =
+ self.is_return_type_anon(scope_def_id_sup, bregion_sup, ty_fndecl_sup);
+ let sub_is_ret_type =
+ self.is_return_type_anon(scope_def_id_sub, bregion_sub, ty_fndecl_sub);
+
+ let span_label_var1 = match anon_param_sup.pat.simple_ident() {
+ Some(simple_ident) => format!(" from `{}`", simple_ident),
+ None => String::new(),
+ };
+
+ let span_label_var2 = match anon_param_sub.pat.simple_ident() {
+ Some(simple_ident) => format!(" into `{}`", simple_ident),
+ None => String::new(),
+ };
+
+ debug!(
+ "try_report_anon_anon_conflict: sub_is_ret_type={:?} sup_is_ret_type={:?}",
+ sub_is_ret_type, sup_is_ret_type
+ );
+
+ let mut err = struct_span_err!(self.tcx().sess, span, E0623, "lifetime mismatch");
+
+ match (sup_is_ret_type, sub_is_ret_type) {
+ (ret_capture @ Some(ret_span), _) | (_, ret_capture @ Some(ret_span)) => {
+ let param_span =
+ if sup_is_ret_type == ret_capture { ty_sub.span } else { ty_sup.span };
+
+ err.span_label(
+ param_span,
+ "this parameter and the return type are declared with different lifetimes...",
+ );
+ err.span_label(ret_span, "");
+ err.span_label(span, format!("...but data{} is returned here", span_label_var1));
+ }
+
+ (None, None) => {
+ if ty_sup.hir_id == ty_sub.hir_id {
+ err.span_label(ty_sup.span, "this type is declared with multiple lifetimes...");
+ err.span_label(ty_sub.span, "");
+ err.span_label(span, "...but data with one lifetime flows into the other here");
+ } else {
+ err.span_label(
+ ty_sup.span,
+ "these two types are declared with different lifetimes...",
+ );
+ err.span_label(ty_sub.span, "");
+ err.span_label(
+ span,
+ format!("...but data{} flows{} here", span_label_var1, span_label_var2),
+ );
+ }
+ }
+ }
+
+ if suggest_adding_lifetime_params(self.tcx(), sub, ty_sup, ty_sub, &mut err) {
+ err.note("each elided lifetime in input position becomes a distinct lifetime");
+ }
+
+ let reported = err.emit();
+ Some(reported)
+ }
+}
+
+pub fn suggest_adding_lifetime_params<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ sub: Region<'tcx>,
+ ty_sup: &Ty<'_>,
+ ty_sub: &Ty<'_>,
+ err: &mut Diagnostic,
+) -> bool {
+ let (
+ hir::Ty { kind: hir::TyKind::Rptr(lifetime_sub, _), .. },
+ hir::Ty { kind: hir::TyKind::Rptr(lifetime_sup, _), .. },
+ ) = (ty_sub, ty_sup) else {
+ return false;
+ };
+
+ if !lifetime_sub.name.is_anonymous() || !lifetime_sup.name.is_anonymous() {
+ return false;
+ };
+
+ let Some(anon_reg) = tcx.is_suitable_region(sub) else {
+ return false;
+ };
+
+ let hir_id = tcx.hir().local_def_id_to_hir_id(anon_reg.def_id);
+
+ let node = tcx.hir().get(hir_id);
+ let is_impl = matches!(&node, hir::Node::ImplItem(_));
+ let generics = match node {
+ hir::Node::Item(&hir::Item { kind: hir::ItemKind::Fn(_, ref generics, ..), .. })
+ | hir::Node::TraitItem(&hir::TraitItem { ref generics, .. })
+ | hir::Node::ImplItem(&hir::ImplItem { ref generics, .. }) => generics,
+ _ => return false,
+ };
+
+ let suggestion_param_name = generics
+ .params
+ .iter()
+ .filter(|p| matches!(p.kind, GenericParamKind::Lifetime { .. }))
+ .map(|p| p.name.ident().name)
+ .find(|i| *i != kw::UnderscoreLifetime);
+ let introduce_new = suggestion_param_name.is_none();
+ let suggestion_param_name =
+ suggestion_param_name.map(|n| n.to_string()).unwrap_or_else(|| "'a".to_owned());
+
+ debug!(?lifetime_sup.span);
+ debug!(?lifetime_sub.span);
+ let make_suggestion = |span: rustc_span::Span| {
+ if span.is_empty() {
+ (span, format!("{}, ", suggestion_param_name))
+ } else if let Ok("&") = tcx.sess.source_map().span_to_snippet(span).as_deref() {
+ (span.shrink_to_hi(), format!("{} ", suggestion_param_name))
+ } else {
+ (span, suggestion_param_name.clone())
+ }
+ };
+ let mut suggestions =
+ vec![make_suggestion(lifetime_sub.span), make_suggestion(lifetime_sup.span)];
+
+ if introduce_new {
+ let new_param_suggestion =
+ if let Some(first) = generics.params.iter().find(|p| !p.name.ident().span.is_empty()) {
+ (first.span.shrink_to_lo(), format!("{}, ", suggestion_param_name))
+ } else {
+ (generics.span, format!("<{}>", suggestion_param_name))
+ };
+
+ suggestions.push(new_param_suggestion);
+ }
+
+ let mut sugg = String::from("consider introducing a named lifetime parameter");
+ if is_impl {
+ sugg.push_str(" and update trait if needed");
+ }
+ err.multipart_suggestion(sugg, suggestions, Applicability::MaybeIncorrect);
+
+ true
+}