Skip to content

Tweak output of missing lifetime on associated type #135602

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions compiler/rustc_hir_analysis/src/check/compare_impl_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1125,13 +1125,13 @@ fn check_region_bounds_on_impl_item<'tcx>(
.expect("expected impl item to have generics or else we can't compare them")
.span;

let mut generics_span = None;
let mut generics_span = tcx.def_span(trait_m.def_id);
let mut bounds_span = vec![];
let mut where_span = None;
if let Some(trait_node) = tcx.hir_get_if_local(trait_m.def_id)
&& let Some(trait_generics) = trait_node.generics()
{
generics_span = Some(trait_generics.span);
generics_span = trait_generics.span;
// FIXME: we could potentially look at the impl's bounds to not point at bounds that
// *are* present in the impl.
for p in trait_generics.predicates {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ pub(crate) struct LifetimesOrBoundsMismatchOnTrait {
#[label]
pub span: Span,
#[label(hir_analysis_generics_label)]
pub generics_span: Option<Span>,
pub generics_span: Span,
#[label(hir_analysis_where_label)]
pub where_span: Option<Span>,
#[label(hir_analysis_bounds_label)]
Expand Down
24 changes: 23 additions & 1 deletion compiler/rustc_hir_analysis/src/impl_wf_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ use std::assert_matches::debug_assert_matches;

use min_specialization::check_min_specialization;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_errors::codes::*;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalDefId;
use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt};
use rustc_span::ErrorGuaranteed;
use rustc_span::{ErrorGuaranteed, kw};

use crate::constrained_generic_params as cgp;
use crate::errors::UnconstrainedGenericParameter;
Expand Down Expand Up @@ -158,6 +159,27 @@ pub(crate) fn enforce_impl_lifetime_params_are_constrained(
const_param_note2: false,
});
diag.code(E0207);
for p in &impl_generics.own_params {
if p.name == kw::UnderscoreLifetime {
let span = tcx.def_span(p.def_id);
let Ok(snippet) = tcx.sess.source_map().span_to_snippet(span) else {
continue;
};

let (span, sugg) = if &snippet == "'_" {
(span, param.name.to_string())
} else {
(span.shrink_to_hi(), format!("{} ", param.name))
};
diag.span_suggestion_verbose(
span,
"consider using the named lifetime here instead of an implict \
lifetime",
sugg,
Applicability::MaybeIncorrect,
);
}
}
res = Err(diag.emit());
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_resolve/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ resolve_added_macro_use =
resolve_ancestor_only =
visibilities can only be restricted to ancestor modules

resolve_anonymous_lifetime_non_gat_report_error =
in the trait associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type
resolve_anonymous_lifetime_non_gat_report_error = missing lifetime in associated type
.label = this lifetime must come from the implemented type
.note = in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type

resolve_arguments_macro_use_not_allowed = arguments to `macro_use` are not allowed here

Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_resolve/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,8 @@ pub(crate) struct AnonymousLivetimeNonGatReportError {
#[primary_span]
#[label]
pub(crate) lifetime: Span,
#[note]
pub(crate) decl: MultiSpan,
}

#[derive(Subdiagnostic)]
Expand Down
142 changes: 135 additions & 7 deletions compiler/rustc_resolve/src/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::borrow::Cow;
use std::collections::BTreeSet;
use std::collections::hash_map::Entry;
use std::mem::{replace, swap, take};
use std::ops::ControlFlow;

use rustc_ast::ptr::P;
use rustc_ast::visit::{
Expand All @@ -20,20 +21,21 @@ use rustc_ast::*;
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
use rustc_errors::codes::*;
use rustc_errors::{
Applicability, DiagArgValue, ErrorGuaranteed, IntoDiagArg, StashKey, Suggestions,
Applicability, Diag, DiagArgValue, ErrorGuaranteed, IntoDiagArg, MultiSpan, StashKey,
Suggestions, pluralize,
};
use rustc_hir::def::Namespace::{self, *};
use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, PartialRes, PerNS};
use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId};
use rustc_hir::{MissingLifetimeKind, PrimTy, TraitCandidate};
use rustc_middle::middle::resolve_bound_vars::Set1;
use rustc_middle::ty::DelegationFnSig;
use rustc_middle::ty::{AssocKind, DelegationFnSig};
use rustc_middle::{bug, span_bug};
use rustc_session::config::{CrateType, ResolveDocLinks};
use rustc_session::lint::{self, BuiltinLintDiag};
use rustc_session::parse::feature_err;
use rustc_span::source_map::{Spanned, respan};
use rustc_span::{BytePos, Ident, Span, Symbol, SyntaxContext, kw, sym};
use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol, SyntaxContext, kw, sym};
use smallvec::{SmallVec, smallvec};
use tracing::{debug, instrument, trace};

Expand Down Expand Up @@ -359,6 +361,7 @@ enum LifetimeBinderKind {
Function,
Closure,
ImplBlock,
ImplAssocType,
Copy link
Member

@Nadrieril Nadrieril Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment to the Item variant explaining which items it covers? Is it only trait assoc types now? If so better rename it.

}

impl LifetimeBinderKind {
Expand All @@ -369,6 +372,7 @@ impl LifetimeBinderKind {
PolyTrait => "bound",
WhereBound => "bound",
Item | ConstItem => "item",
ImplAssocType => "associated type",
ImplBlock => "impl block",
Function => "function",
Closure => "closure",
Expand Down Expand Up @@ -684,6 +688,9 @@ struct DiagMetadata<'ast> {
/// The current impl items (used to suggest).
current_impl_items: Option<&'ast [P<AssocItem>]>,

/// The current impl items (used to suggest).
current_impl_item: Option<&'ast AssocItem>,

/// When processing impl trait
currently_processing_impl_trait: Option<(TraitRef, Ty)>,

Expand Down Expand Up @@ -1870,9 +1877,34 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
ty: ty.span,
});
} else {
self.r.dcx().emit_err(errors::AnonymousLivetimeNonGatReportError {
lifetime: lifetime.ident.span,
});
let decl = if !trait_id.is_local()
&& let Some(assoc) = self.diag_metadata.current_impl_item
&& let AssocItemKind::Type(_) = assoc.kind
&& let assocs = self.r.tcx.associated_items(trait_id)
&& let Some(assoc) = assocs.find_by_name_and_kind(
self.r.tcx,
assoc.ident,
AssocKind::Type,
trait_id,
) {
let mut decl: MultiSpan =
self.r.tcx.def_span(assoc.def_id).into();
decl.push_span_label(
self.r.tcx.def_span(trait_id),
String::new(),
);
decl
} else {
DUMMY_SP.into()
};
let mut err = self.r.dcx().create_err(
errors::AnonymousLivetimeNonGatReportError {
lifetime: lifetime.ident.span,
decl,
},
);
self.point_at_impl_lifetimes(&mut err, i, lifetime.ident.span);
err.emit();
}
} else {
self.r.dcx().emit_err(errors::ElidedAnonymousLivetimeReportError {
Expand Down Expand Up @@ -1909,6 +1941,99 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
self.report_missing_lifetime_specifiers(vec![missing_lifetime], None);
}

fn point_at_impl_lifetimes(&mut self, err: &mut Diag<'_>, i: usize, lifetime: Span) {
let Some((rib, span)) = self.lifetime_ribs[..i]
.iter()
.rev()
.skip(1)
Copy link
Member

@Nadrieril Nadrieril Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the skip? Could you add a comment explaining it?

.filter_map(|rib| match rib.kind {
LifetimeRibKind::Generics { span, kind: LifetimeBinderKind::ImplBlock, .. } => {
Some((rib, span))
}
_ => None,
})
.next()
Comment on lines +1949 to +1955
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
.filter_map(|rib| match rib.kind {
LifetimeRibKind::Generics { span, kind: LifetimeBinderKind::ImplBlock, .. } => {
Some((rib, span))
}
_ => None,
})
.next()
.find_map(|rib| match rib.kind {
LifetimeRibKind::Generics { span, kind: LifetimeBinderKind::ImplBlock, .. } => {
Some((rib, span))
}
_ => None,
})

else {
return;
};
if !rib.bindings.is_empty() {
err.span_label(
span,
format!(
"there {} named lifetime{} specified on the impl block you could use",
if rib.bindings.len() == 1 { "is a" } else { "are" },
pluralize!(rib.bindings.len()),
),
);
if rib.bindings.len() == 1 {
err.span_suggestion_verbose(
lifetime.shrink_to_hi(),
"consider using the lifetime from the impl block",
format!("{} ", rib.bindings.keys().next().unwrap()),
Applicability::MaybeIncorrect,
);
}
} else {
struct AnonRefFinder;
impl<'ast> Visitor<'ast> for AnonRefFinder {
type Result = ControlFlow<Span>;

fn visit_ty(&mut self, ty: &'ast ast::Ty) -> Self::Result {
if let ast::TyKind::Ref(None, mut_ty) = &ty.kind {
return ControlFlow::Break(mut_ty.ty.span.shrink_to_lo());
}
visit::walk_ty(self, ty)
}

fn visit_lifetime(
&mut self,
lt: &'ast ast::Lifetime,
_cx: visit::LifetimeCtxt,
) -> Self::Result {
if lt.ident.name == kw::UnderscoreLifetime {
return ControlFlow::Break(lt.ident.span);
}
visit::walk_lifetime(self, lt)
}
}

if let Some(ty) = &self.diag_metadata.current_self_type
&& let ControlFlow::Break(sp) = AnonRefFinder.visit_ty(ty)
{
err.multipart_suggestion_verbose(
"add a lifetime to the impl block and use it in the self type and associated \
type",
vec![
(span, "<'a>".to_string()),
(sp, "'a ".to_string()),
(lifetime.shrink_to_hi(), "'a ".to_string()),
],
Applicability::MaybeIncorrect,
);
} else if let Some(item) = &self.diag_metadata.current_item
&& let ItemKind::Impl(impl_) = &item.kind
&& let Some(of_trait) = &impl_.of_trait
&& let ControlFlow::Break(sp) = AnonRefFinder.visit_trait_ref(of_trait)
{
err.multipart_suggestion_verbose(
"add a lifetime to the impl block and use it in the trait and associated type",
vec![
(span, "<'a>".to_string()),
(sp, "'a".to_string()),
(lifetime.shrink_to_hi(), "'a ".to_string()),
],
Applicability::MaybeIncorrect,
);
} else {
err.span_label(
span,
"you could add a lifetime on the impl block, if the trait or the self type \
could have one",
);
}
}
}

#[instrument(level = "debug", skip(self))]
fn resolve_elided_lifetime(&mut self, anchor_id: NodeId, span: Span) {
let id = self.r.next_node_id();
Expand Down Expand Up @@ -3265,6 +3390,8 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
) {
use crate::ResolutionError::*;
self.resolve_doc_links(&item.attrs, MaybeExported::ImplItem(trait_id.ok_or(&item.vis)));
let prev = self.diag_metadata.current_impl_item.take();
self.diag_metadata.current_impl_item = Some(&item);
match &item.kind {
AssocItemKind::Const(box ast::ConstItem { generics, ty, expr, .. }) => {
debug!("resolve_implementation AssocItemKind::Const");
Expand Down Expand Up @@ -3349,7 +3476,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
LifetimeRibKind::Generics {
binder: item.id,
span: generics.span,
kind: LifetimeBinderKind::Item,
kind: LifetimeBinderKind::ImplAssocType,
},
|this| {
this.with_lifetime_rib(LifetimeRibKind::AnonymousReportError, |this| {
Expand Down Expand Up @@ -3400,6 +3527,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
panic!("unexpanded macro in resolve!")
}
}
self.diag_metadata.current_impl_item = prev;
}

fn check_trait_item<F>(
Expand Down
10 changes: 4 additions & 6 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2933,15 +2933,10 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
&mut err,
Some(lifetime_ref.ident.name.as_str()),
|err, _, span, message, suggestion, span_suggs| {
err.multipart_suggestion_with_style(
err.multipart_suggestion_verbose(
message,
std::iter::once((span, suggestion)).chain(span_suggs.clone()).collect(),
Applicability::MaybeIncorrect,
if span_suggs.is_empty() {
SuggestionStyle::ShowCode
} else {
SuggestionStyle::ShowAlways
},
);
true
},
Expand Down Expand Up @@ -2976,6 +2971,9 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
{
continue;
}
if let LifetimeBinderKind::ImplAssocType = kind {
continue;
}

if !span.can_be_used_for_suggestions()
&& suggest_note
Expand Down
9 changes: 6 additions & 3 deletions tests/ui/associated-inherent-types/issue-109299.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ error[E0261]: use of undeclared lifetime name `'d`
--> $DIR/issue-109299.rs:6:12
|
LL | impl Lexer<'d> {
| - ^^ undeclared lifetime
| |
| help: consider introducing lifetime `'d` here: `<'d>`
| ^^ undeclared lifetime
|
help: consider introducing lifetime `'d` here
|
LL | impl<'d> Lexer<'d> {
| ++++

error: aborting due to 1 previous error

Expand Down
17 changes: 11 additions & 6 deletions tests/ui/borrowck/generic_const_early_param.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@ LL | struct DataWrapper<'static> {
error[E0261]: use of undeclared lifetime name `'a`
--> $DIR/generic_const_early_param.rs:6:12
|
LL | struct DataWrapper<'static> {
| - help: consider introducing lifetime `'a` here: `'a,`
LL |
LL | data: &'a [u8; Self::SIZE],
| ^^ undeclared lifetime
|
help: consider introducing lifetime `'a` here
|
LL | struct DataWrapper<'a, 'static> {
| +++

error[E0261]: use of undeclared lifetime name `'a`
--> $DIR/generic_const_early_param.rs:10:18
|
LL | impl DataWrapper<'a> {
| - ^^ undeclared lifetime
| |
| help: consider introducing lifetime `'a` here: `<'a>`
| ^^ undeclared lifetime
|
help: consider introducing lifetime `'a` here
|
LL | impl<'a> DataWrapper<'a> {
| ++++

warning: the feature `generic_const_exprs` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/generic_const_early_param.rs:1:12
Expand Down
Loading
Loading