Lint single-use-lifetimes on the AST.

This commit is contained in:
Camille GILLOT 2022-05-10 21:15:30 +02:00
parent db8a9274a9
commit 563916d698
19 changed files with 364 additions and 469 deletions

View File

@ -819,6 +819,43 @@ pub trait LintContext: Sized {
"see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information",
);
},
BuiltinLintDiagnostics::SingleUseLifetime {
param_span,
use_span: Some((use_span, elide)),
deletion_span,
} => {
debug!(?param_span, ?use_span, ?deletion_span);
db.span_label(param_span, "this lifetime...");
db.span_label(use_span, "...is used only here");
let msg = "elide the single-use lifetime";
let (use_span, replace_lt) = if elide {
let use_span = sess.source_map().span_extend_while(
use_span,
char::is_whitespace,
).unwrap_or(use_span);
(use_span, String::new())
} else {
(use_span, "'_".to_owned())
};
db.multipart_suggestion(
msg,
vec![(deletion_span, String::new()), (use_span, replace_lt)],
Applicability::MachineApplicable,
);
},
BuiltinLintDiagnostics::SingleUseLifetime {
param_span: _,
use_span: None,
deletion_span,
} => {
debug!(?deletion_span);
db.span_suggestion(
deletion_span,
"elide the unused lifetime",
String::new(),
Applicability::MachineApplicable,
);
},
}
// Rewrap `db`, and pass control to the user.
decorate(LintDiagnosticBuilder::new(db));

View File

@ -239,6 +239,7 @@ impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T>
fn visit_generic_param(&mut self, param: &'a ast::GenericParam) {
run_early_pass!(self, check_generic_param, param);
self.check_id(param.id);
ast_visit::walk_generic_param(self, param);
}

View File

@ -14,7 +14,7 @@ use rustc_middle::lint::{
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{RegisteredTools, TyCtxt};
use rustc_session::lint::{
builtin::{self, FORBIDDEN_LINT_GROUPS, UNFULFILLED_LINT_EXPECTATIONS},
builtin::{self, FORBIDDEN_LINT_GROUPS, SINGLE_USE_LIFETIMES, UNFULFILLED_LINT_EXPECTATIONS},
Level, Lint, LintExpectationId, LintId,
};
use rustc_session::parse::{add_feature_diagnostics, feature_err};
@ -259,6 +259,14 @@ impl<'s> LintLevelsBuilder<'s> {
let sess = self.sess;
let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
for (attr_index, attr) in attrs.iter().enumerate() {
if attr.has_name(sym::automatically_derived) {
self.current_specs_mut().insert(
LintId::of(SINGLE_USE_LIFETIMES),
(Level::Allow, LintLevelSource::Default),
);
continue;
}
let level = match Level::from_attr(attr) {
None => continue,
Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => {

View File

@ -423,7 +423,11 @@ pub enum BuiltinLintDiagnostics {
DeprecatedMacro(Option<Symbol>, Span),
MissingAbi(Span, Abi),
UnusedDocComment(Span),
UnusedBuiltinAttribute { attr_name: Symbol, macro_name: String, invoc_span: Span },
UnusedBuiltinAttribute {
attr_name: Symbol,
macro_name: String,
invoc_span: Span,
},
PatternsInFnsWithoutBody(Span, Ident),
LegacyDeriveHelpers(Span),
ProcMacroBackCompat(String),
@ -435,6 +439,16 @@ pub enum BuiltinLintDiagnostics {
UnicodeTextFlow(Span, String),
UnexpectedCfg((Symbol, Span), Option<(Symbol, Span)>),
DeprecatedWhereclauseLocation(Span, String),
SingleUseLifetime {
/// Span of the parameter which declares this lifetime.
param_span: Span,
/// Span of the code that should be removed when eliding this lifetime.
/// This span should include leading or trailing comma.
deletion_span: Span,
/// Span of the single use, or None if the lifetime is never used.
/// If true, the lifetime will be fully elided.
use_span: Option<(Span, bool)>,
},
}
/// Lints that are buffered up early on in the `Session` before the

View File

@ -1,3 +1,4 @@
// ignore-tidy-filelength
//! "Late resolution" is the pass that resolves most of names in a crate beside imports and macros.
//! It runs when the crate is fully expanded and its module structure is fully built.
//! So it just walks through the crate and resolves all the expressions, types, etc.
@ -19,7 +20,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
use rustc_errors::DiagnosticId;
use rustc_hir::def::Namespace::{self, *};
use rustc_hir::def::{self, CtorKind, DefKind, PartialRes, PerNS};
use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID};
use rustc_hir::definitions::DefPathData;
use rustc_hir::{PrimTy, TraitCandidate};
use rustc_index::vec::Idx;
@ -197,13 +198,19 @@ impl<'a, R> Rib<'a, R> {
}
}
#[derive(Clone, Copy, Debug)]
enum LifetimeUseSet {
One { use_span: Span, use_ctxt: visit::LifetimeCtxt },
Many,
}
#[derive(Copy, Clone, Debug)]
enum LifetimeRibKind {
/// This rib acts as a barrier to forbid reference to lifetimes of a parent item.
Item,
/// This rib declares generic parameters.
Generics { parent: NodeId, span: Span, kind: LifetimeBinderKind },
Generics { binder: NodeId, span: Span, kind: LifetimeBinderKind },
/// FIXME(const_generics): This patches over an ICE caused by non-'static lifetimes in const
/// generics. We are disallowing this until we can decide on how we want to handle non-'static
@ -230,7 +237,7 @@ enum LifetimeRibKind {
AnonymousReportError,
/// Pass responsibility to `resolve_lifetime` code for all cases.
AnonymousPassThrough(NodeId),
AnonymousPassThrough(NodeId, /* in_fn_return */ bool),
}
#[derive(Copy, Clone, Debug)]
@ -519,6 +526,9 @@ struct LateResolutionVisitor<'a, 'b, 'ast> {
/// In most cases this will be `None`, in which case errors will always be reported.
/// If it is `true`, then it will be updated when entering a nested function or trait body.
in_func_body: bool,
/// Count the number of places a lifetime is used.
lifetime_uses: FxHashMap<LocalDefId, LifetimeUseSet>,
}
/// Walks the whole crate in DFS order, visiting each item, resolving names as it goes.
@ -599,17 +609,19 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
&bare_fn.generic_params,
NormalRibKind,
LifetimeRibKind::Generics {
parent: ty.id,
binder: ty.id,
kind: LifetimeBinderKind::BareFnType,
span,
},
|this| {
this.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(ty.id),
|this| {
this.visit_generic_param_vec(&bare_fn.generic_params, false);
visit::walk_fn_decl(this, &bare_fn.decl);
},
this.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(ty.id, false),
|this| walk_list!(this, visit_param, &bare_fn.decl.inputs),
);
this.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(ty.id, true),
|this| this.visit_fn_ret_ty(&bare_fn.decl.output),
);
},
);
@ -628,7 +640,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
&tref.bound_generic_params,
NormalRibKind,
LifetimeRibKind::Generics {
parent: tref.trait_ref.ref_id,
binder: tref.trait_ref.ref_id,
kind: LifetimeBinderKind::PolyTrait,
span,
},
@ -652,7 +664,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
&generics.params,
ItemRibKind(HasGenericParams::Yes),
LifetimeRibKind::Generics {
parent: foreign_item.id,
binder: foreign_item.id,
kind: LifetimeBinderKind::Item,
span: generics.span,
},
@ -666,7 +678,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
&generics.params,
ItemRibKind(HasGenericParams::Yes),
LifetimeRibKind::Generics {
parent: foreign_item.id,
binder: foreign_item.id,
kind: LifetimeBinderKind::Function,
span: generics.span,
},
@ -690,13 +702,20 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
// a body, or if there's no body for some other reason.
FnKind::Fn(FnCtxt::Foreign, _, sig, _, generics, _)
| FnKind::Fn(_, _, sig, _, generics, None) => {
self.with_lifetime_rib(LifetimeRibKind::AnonymousPassThrough(fn_id), |this| {
// We don't need to deal with patterns in parameters, because
// they are not possible for foreign or bodiless functions.
self.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(fn_id, false),
|this| {
this.visit_fn_header(&sig.header);
this.visit_generics(generics);
visit::walk_fn_decl(this, &sig.decl);
});
walk_list!(this, visit_param, &sig.decl.inputs);
},
);
self.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(fn_id, true),
|this| this.visit_fn_ret_ty(&sig.decl.output),
);
return;
}
FnKind::Fn(FnCtxt::Free, ..) => FnItemRibKind,
@ -759,28 +778,32 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
this.r.extra_lifetime_params_map.insert(async_node_id, extra_lifetime_params);
this.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(async_node_id),
LifetimeRibKind::AnonymousPassThrough(async_node_id, true),
|this| visit::walk_fn_ret_ty(this, &declaration.output),
);
} else {
this.with_lifetime_rib(LifetimeRibKind::AnonymousPassThrough(fn_id), |this| {
// Add each argument to the rib.
this.resolve_params(&declaration.inputs);
visit::walk_fn_ret_ty(this, &declaration.output);
});
this.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(fn_id, false),
|this| this.resolve_params(&declaration.inputs),
);
this.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(fn_id, true),
|this| visit::walk_fn_ret_ty(this, &declaration.output),
);
};
// Ignore errors in function bodies if this is rustdoc
// Be sure not to set this until the function signature has been resolved.
let previous_state = replace(&mut this.in_func_body, true);
// Resolve the function body, potentially inside the body of an async closure
this.with_lifetime_rib(LifetimeRibKind::AnonymousPassThrough(fn_id), |this| {
match fn_kind {
this.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(fn_id, false),
|this| match fn_kind {
FnKind::Fn(.., body) => walk_list!(this, visit_block, body),
FnKind::Closure(_, body) => this.visit_expr(body),
}
});
},
);
debug!("(resolving function) leaving function");
this.in_func_body = previous_state;
@ -788,8 +811,8 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
});
self.diagnostic_metadata.current_function = previous_value;
}
fn visit_lifetime(&mut self, lifetime: &'ast Lifetime, _: visit::LifetimeCtxt) {
self.resolve_lifetime(lifetime)
fn visit_lifetime(&mut self, lifetime: &'ast Lifetime, use_ctxt: visit::LifetimeCtxt) {
self.resolve_lifetime(lifetime, use_ctxt)
}
fn visit_generics(&mut self, generics: &'ast Generics) {
@ -864,10 +887,16 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
if let Some(ref args) = path_segment.args {
match &**args {
GenericArgs::AngleBracketed(..) => visit::walk_generic_args(self, path_span, args),
GenericArgs::Parenthesized(..) => self.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(path_segment.id),
|this| visit::walk_generic_args(this, path_span, args),
),
GenericArgs::Parenthesized(ref data) => {
self.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(path_segment.id, false),
|this| walk_list!(this, visit_ty, &data.inputs),
);
self.with_lifetime_rib(
LifetimeRibKind::AnonymousPassThrough(path_segment.id, true),
|this| visit::walk_fn_ret_ty(this, &data.output),
)
}
}
}
}
@ -890,7 +919,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
&bound_generic_params,
NormalRibKind,
LifetimeRibKind::Generics {
parent: bounded_ty.id,
binder: bounded_ty.id,
kind: LifetimeBinderKind::WhereBound,
span,
},
@ -971,6 +1000,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
diagnostic_metadata: DiagnosticMetadata::default(),
// errors at module scope should always be reported
in_func_body: false,
lifetime_uses: Default::default(),
}
}
@ -1178,7 +1208,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
}
#[tracing::instrument(level = "debug", skip(self))]
fn resolve_lifetime(&mut self, lifetime: &'ast Lifetime) {
fn resolve_lifetime(&mut self, lifetime: &'ast Lifetime, use_ctxt: visit::LifetimeCtxt) {
let ident = lifetime.ident;
if ident.name == kw::StaticLifetime {
@ -1196,6 +1226,40 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
let normalized_ident = ident.normalize_to_macros_2_0();
if let Some(&(_, region)) = rib.bindings.get(&normalized_ident) {
self.record_lifetime_res(lifetime.id, region);
if let LifetimeRes::Param { param, .. } = region {
match self.lifetime_uses.entry(param) {
Entry::Vacant(v) => {
debug!("First use of {:?} at {:?}", region, ident.span);
let use_set = self
.lifetime_ribs
.iter()
.rev()
.find_map(|rib| match rib.kind {
// Do not suggest eliding a lifetime where an anonymous
// lifetime would be illegal.
LifetimeRibKind::Item
| LifetimeRibKind::AnonymousPassThrough(_, true)
| LifetimeRibKind::AnonymousReportError => {
Some(LifetimeUseSet::Many)
}
// An anonymous lifetime is legal here, go ahead.
LifetimeRibKind::AnonymousPassThrough(_, false)
| LifetimeRibKind::AnonymousCreateParameter(_) => {
Some(LifetimeUseSet::One { use_span: ident.span, use_ctxt })
}
_ => None,
})
.unwrap_or(LifetimeUseSet::Many);
debug!(?use_ctxt, ?use_set);
v.insert(use_set);
}
Entry::Occupied(mut o) => {
debug!("Many uses of {:?} at {:?}", region, ident.span);
*o.get_mut() = LifetimeUseSet::Many;
}
}
}
return;
}
@ -1262,7 +1326,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
self.record_lifetime_res(lifetime.id, LifetimeRes::Error);
return;
}
LifetimeRibKind::AnonymousPassThrough(node_id) => {
LifetimeRibKind::AnonymousPassThrough(node_id, _) => {
self.record_lifetime_res(
lifetime.id,
LifetimeRes::Anonymous { binder: node_id, elided },
@ -1382,7 +1446,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
// `PathSegment`, for which there is no associated `'_` or `&T` with no explicit
// lifetime. Instead, we simply create an implicit lifetime, which will be checked
// later, at which point a suitable error will be emitted.
LifetimeRibKind::AnonymousPassThrough(binder) => {
LifetimeRibKind::AnonymousPassThrough(binder, _) => {
res = LifetimeRes::Anonymous { binder, elided: true };
break;
}
@ -1550,7 +1614,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
&generics.params,
ItemRibKind(HasGenericParams::Yes),
LifetimeRibKind::Generics {
parent: item.id,
binder: item.id,
kind: LifetimeBinderKind::Item,
span: generics.span,
},
@ -1620,7 +1684,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
&generics.params,
ItemRibKind(HasGenericParams::Yes),
LifetimeRibKind::Generics {
parent: item.id,
binder: item.id,
kind: LifetimeBinderKind::Item,
span: generics.span,
},
@ -1633,7 +1697,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
&generics.params,
ItemRibKind(HasGenericParams::Yes),
LifetimeRibKind::Generics {
parent: item.id,
binder: item.id,
kind: LifetimeBinderKind::Function,
span: generics.span,
},
@ -1665,7 +1729,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
&generics.params,
ItemRibKind(HasGenericParams::Yes),
LifetimeRibKind::Generics {
parent: item.id,
binder: item.id,
kind: LifetimeBinderKind::Item,
span: generics.span,
},
@ -1686,7 +1750,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
&generics.params,
AssocItemRibKind,
LifetimeRibKind::Generics {
parent: item.id,
binder: item.id,
span: generics.span,
kind,
},
@ -1754,7 +1818,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
&generics.params,
ItemRibKind(HasGenericParams::Yes),
LifetimeRibKind::Generics {
parent: item.id,
binder: item.id,
kind: LifetimeBinderKind::Item,
span: generics.span,
},
@ -1824,6 +1888,9 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
F: FnOnce(&mut Self),
{
debug!("with_generic_param_rib");
let LifetimeRibKind::Generics { binder, span: generics_span, kind: generics_kind, .. }
= lifetime_kind else { panic!() };
let mut function_type_rib = Rib::new(kind);
let mut function_value_rib = Rib::new(kind);
let mut function_lifetime_rib = LifetimeRib::new(lifetime_kind);
@ -1892,8 +1959,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
GenericParamKind::Type { .. } => (&mut function_type_rib, DefKind::TyParam),
GenericParamKind::Const { .. } => (&mut function_value_rib, DefKind::ConstParam),
GenericParamKind::Lifetime => {
let LifetimeRibKind::Generics { parent, .. } = lifetime_kind else { panic!() };
let res = LifetimeRes::Param { param: def_id, binder: parent };
let res = LifetimeRes::Param { param: def_id, binder };
self.record_lifetime_res(param.id, res);
function_lifetime_rib.bindings.insert(ident, (param.id, res));
continue;
@ -1913,6 +1979,14 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
self.ribs[TypeNS].pop();
self.ribs[ValueNS].pop();
self.lifetime_ribs.pop();
if let LifetimeBinderKind::BareFnType
| LifetimeBinderKind::WhereBound
| LifetimeBinderKind::Function
| LifetimeBinderKind::ImplBlock = generics_kind
{
self.maybe_report_lifetime_uses(generics_span, params)
}
}
fn with_label_rib(&mut self, kind: RibKind<'a>, f: impl FnOnce(&mut Self)) {
@ -2039,7 +2113,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
) {
debug!("resolve_implementation");
// If applicable, create a rib for the type parameters.
self.with_generic_param_rib(&generics.params, ItemRibKind(HasGenericParams::Yes), LifetimeRibKind::Generics { span: generics.span, parent: item_id, kind: LifetimeBinderKind::ImplBlock }, |this| {
self.with_generic_param_rib(&generics.params, ItemRibKind(HasGenericParams::Yes), LifetimeRibKind::Generics { span: generics.span, binder: item_id, kind: LifetimeBinderKind::ImplBlock }, |this| {
// Dummy self type for better errors if `Self` is used in the trait path.
this.with_self_rib(Res::SelfTy { trait_: None, alias_to: None }, |this| {
this.with_lifetime_rib(LifetimeRibKind::AnonymousCreateParameter(item_id), |this| {
@ -2066,7 +2140,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
this.visit_generics(generics);
// Resolve the items within the impl.
this.with_lifetime_rib(LifetimeRibKind::AnonymousPassThrough(item_id),
this.with_lifetime_rib(LifetimeRibKind::AnonymousPassThrough(item_id,false),
|this| {
this.with_current_self_type(self_type, |this| {
this.with_self_rib_ns(ValueNS, Res::SelfCtor(item_def_id), |this| {
@ -2111,7 +2185,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
this.with_generic_param_rib(
&generics.params,
AssocItemRibKind,
LifetimeRibKind::Generics { parent: item.id, span: generics.span, kind: LifetimeBinderKind::Function },
LifetimeRibKind::Generics { binder: item.id, span: generics.span, kind: LifetimeBinderKind::Function },
|this| {
// If this is a trait impl, ensure the method
// exists in trait
@ -2140,7 +2214,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
this.with_generic_param_rib(
&generics.params,
AssocItemRibKind,
LifetimeRibKind::Generics { parent: item.id, span: generics.span, kind: LifetimeBinderKind::Item },
LifetimeRibKind::Generics { binder: item.id, span: generics.span, kind: LifetimeBinderKind::Item },
|this| {
// If this is a trait impl, ensure the type
// exists in trait

View File

@ -1,16 +1,17 @@
use crate::diagnostics::{ImportSuggestion, LabelSuggestion, TypoSuggestion};
use crate::late::lifetimes::{ElisionFailureInfo, LifetimeContext};
use crate::late::{AliasPossibility, LateResolutionVisitor, RibKind};
use crate::late::{LifetimeBinderKind, LifetimeRibKind};
use crate::late::{LifetimeBinderKind, LifetimeRibKind, LifetimeUseSet};
use crate::path_names_to_string;
use crate::{Module, ModuleKind, ModuleOrUniformRoot};
use crate::{PathResult, PathSource, Segment};
use rustc_ast::visit::{FnCtxt, FnKind};
use rustc_ast::visit::{FnCtxt, FnKind, LifetimeCtxt};
use rustc_ast::{
self as ast, AssocItemKind, Expr, ExprKind, GenericParam, GenericParamKind, Item, ItemKind,
NodeId, Path, Ty, TyKind,
};
use rustc_ast_lowering::ResolverAstLowering;
use rustc_ast_pretty::pprust::path_segment_to_string;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::{
@ -22,6 +23,7 @@ use rustc_hir::def::Namespace::{self, *};
use rustc_hir::def::{self, CtorKind, CtorOf, DefKind};
use rustc_hir::def_id::{DefId, CRATE_DEF_ID, LOCAL_CRATE};
use rustc_hir::PrimTy;
use rustc_session::lint;
use rustc_session::parse::feature_err;
use rustc_span::edition::Edition;
use rustc_span::hygiene::MacroKind;
@ -1832,6 +1834,76 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
})
}
crate fn maybe_report_lifetime_uses(
&mut self,
generics_span: Span,
params: &[ast::GenericParam],
) {
for (param_index, param) in params.iter().enumerate() {
let GenericParamKind::Lifetime = param.kind else { continue };
let def_id = self.r.local_def_id(param.id);
let use_set = self.lifetime_uses.remove(&def_id);
debug!(
"Use set for {:?}({:?} at {:?}) is {:?}",
def_id, param.ident, param.ident.span, use_set
);
let deletion_span = || {
if params.len() == 1 {
// if sole lifetime, remove the entire `<>` brackets
generics_span
} else if param_index == 0 {
// if removing within `<>` brackets, we also want to
// delete a leading or trailing comma as appropriate
param.span().to(params[param_index + 1].span().shrink_to_lo())
} else {
// if removing within `<>` brackets, we also want to
// delete a leading or trailing comma as appropriate
params[param_index - 1].span().shrink_to_hi().to(param.span())
}
};
match use_set {
Some(LifetimeUseSet::Many) => {}
Some(LifetimeUseSet::One { use_span, use_ctxt }) => {
debug!(?param.ident, ?param.ident.span, ?use_span);
let elidable = matches!(use_ctxt, LifetimeCtxt::Rptr);
let deletion_span = deletion_span();
self.r.lint_buffer.buffer_lint_with_diagnostic(
lint::builtin::SINGLE_USE_LIFETIMES,
param.id,
param.ident.span,
&format!("lifetime parameter `{}` only used once", param.ident),
lint::BuiltinLintDiagnostics::SingleUseLifetime {
param_span: param.ident.span,
use_span: Some((use_span, elidable)),
deletion_span,
},
);
}
None => {
debug!(?param.ident, ?param.ident.span);
let deletion_span = deletion_span();
self.r.lint_buffer.buffer_lint_with_diagnostic(
lint::builtin::UNUSED_LIFETIMES,
param.id,
param.ident.span,
&format!("lifetime parameter `{}` never used", param.ident),
lint::BuiltinLintDiagnostics::SingleUseLifetime {
param_span: param.ident.span,
use_span: None,
deletion_span,
},
);
}
}
}
}
crate fn emit_undeclared_lifetime_error(
&self,
lifetime_ref: &ast::Lifetime,
@ -1863,7 +1935,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
for rib in self.lifetime_ribs.iter().rev() {
match rib.kind {
LifetimeRibKind::Generics { parent: _, span, kind } => {
LifetimeRibKind::Generics { binder: _, span, kind } => {
if !span.can_be_used_for_suggestions() && suggest_note {
suggest_note = false; // Avoid displaying the same help multiple times.
err.span_label(

View File

@ -9,20 +9,19 @@
use crate::late::diagnostics::{ForLifetimeSpanType, MissingLifetimeSpot};
use rustc_ast::walk_list;
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
use rustc_errors::{struct_span_err, Applicability, Diagnostic};
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefIdMap, LocalDefId};
use rustc_hir::hir_id::ItemLocalId;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{GenericArg, GenericParam, LifetimeName, Node, ParamName, QPath};
use rustc_hir::{GenericArg, GenericParam, LifetimeName, Node, ParamName};
use rustc_hir::{GenericParamKind, HirIdMap, HirIdSet};
use rustc_middle::hir::map::Map;
use rustc_middle::hir::nested_filter;
use rustc_middle::middle::resolve_lifetime::*;
use rustc_middle::ty::{self, DefIdTree, GenericParamDefKind, TyCtxt};
use rustc_middle::{bug, span_bug};
use rustc_session::lint;
use rustc_span::def_id::DefId;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::Span;
@ -33,13 +32,6 @@ use std::mem::take;
use tracing::{debug, span, Level};
// This counts the no of times a lifetime is used
#[derive(Clone, Copy, Debug)]
pub enum LifetimeUseSet<'tcx> {
One(&'tcx hir::Lifetime),
Many,
}
trait RegionExt {
fn early(hir_map: Map<'_>, index: &mut u32, param: &GenericParam<'_>) -> (ParamName, Region);
@ -175,8 +167,6 @@ crate struct LifetimeContext<'a, 'tcx> {
/// Cache for cross-crate per-definition object lifetime defaults.
xcrate_object_lifetime_defaults: DefIdMap<Vec<ObjectLifetimeDefault>>,
lifetime_uses: &'a mut DefIdMap<LifetimeUseSet<'tcx>>,
/// When encountering an undefined named lifetime, we will suggest introducing it in these
/// places.
crate missing_named_lifetime_spots: Vec<MissingLifetimeSpot<'tcx>>,
@ -197,11 +187,6 @@ enum Scope<'a> {
/// we should use for an early-bound region?
next_early_index: u32,
/// Flag is set to true if, in this binder, `'_` would be
/// equivalent to a "single-use region". This is true on
/// impls, but not other kinds of items.
track_lifetime_uses: bool,
/// Whether or not this binder would serve as the parent
/// binder for opaque types introduced within. For example:
///
@ -297,7 +282,6 @@ impl<'a> fmt::Debug for TruncatedScopeDebug<'a> {
Scope::Binder {
lifetimes,
next_early_index,
track_lifetime_uses,
opaque_type_parent,
scope_type,
hir_id,
@ -307,7 +291,6 @@ impl<'a> fmt::Debug for TruncatedScopeDebug<'a> {
.debug_struct("Binder")
.field("lifetimes", lifetimes)
.field("next_early_index", next_early_index)
.field("track_lifetime_uses", track_lifetime_uses)
.field("opaque_type_parent", opaque_type_parent)
.field("scope_type", scope_type)
.field("hir_id", hir_id)
@ -453,7 +436,6 @@ fn do_resolve(
trait_definition_only,
labels_in_fn: vec![],
xcrate_object_lifetime_defaults: Default::default(),
lifetime_uses: &mut Default::default(),
missing_named_lifetime_spots: vec![],
};
visitor.visit_item(item);
@ -697,7 +679,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
lifetimes: FxIndexMap::default(),
next_early_index: self.next_early_index(),
s: self.scope,
track_lifetime_uses: true,
opaque_type_parent: false,
scope_type: BinderScopeType::Normal,
allow_late_bound: true,
@ -796,9 +777,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
| hir::ItemKind::Impl(hir::Impl { ref generics, .. }) => {
self.missing_named_lifetime_spots.push(generics.into());
// Impls permit `'_` to be used and it is equivalent to "some fresh lifetime name".
// This is not true for other kinds of items.
let track_lifetime_uses = matches!(item.kind, hir::ItemKind::Impl { .. });
// These kinds of items have only early-bound lifetime parameters.
let mut index = if sub_items_have_self_param(&item.kind) {
1 // Self comes before lifetimes
@ -825,7 +803,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
lifetimes,
next_early_index: index + non_lifetime_count,
opaque_type_parent: true,
track_lifetime_uses,
scope_type: BinderScopeType::Normal,
s: ROOT_SCOPE,
allow_late_bound: false,
@ -892,7 +869,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
lifetimes,
s: self.scope,
next_early_index,
track_lifetime_uses: true,
opaque_type_parent: false,
scope_type: BinderScopeType::Normal,
allow_late_bound: true,
@ -1053,11 +1029,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
match param.kind {
GenericParamKind::Lifetime { .. } => {
let (name, reg) = Region::early(self.tcx.hir(), &mut index, &param);
let Region::EarlyBound(_, def_id) = reg else {
bug!();
};
// We cannot predict what lifetimes are unused in opaque type.
self.lifetime_uses.insert(def_id, LifetimeUseSet::Many);
if let hir::ParamName::Plain(Ident {
name: kw::UnderscoreLifetime,
..
@ -1087,7 +1058,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
lifetimes,
next_early_index,
s: this.scope,
track_lifetime_uses: true,
opaque_type_parent: false,
scope_type: BinderScopeType::Normal,
allow_late_bound: false,
@ -1108,7 +1078,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
lifetimes,
next_early_index,
s: self.scope,
track_lifetime_uses: true,
opaque_type_parent: false,
scope_type: BinderScopeType::Normal,
allow_late_bound: false,
@ -1168,7 +1137,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
lifetimes,
next_early_index: index + non_lifetime_count,
s: self.scope,
track_lifetime_uses: true,
opaque_type_parent: true,
scope_type: BinderScopeType::Normal,
allow_late_bound: false,
@ -1238,7 +1206,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
lifetimes,
next_early_index: index + non_lifetime_count,
s: self.scope,
track_lifetime_uses: true,
opaque_type_parent: true,
scope_type: BinderScopeType::Normal,
allow_late_bound: true,
@ -1383,7 +1350,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
lifetimes,
s: this.scope,
next_early_index,
track_lifetime_uses: true,
opaque_type_parent: false,
scope_type: BinderScopeType::Normal,
allow_late_bound: true,
@ -1457,7 +1423,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
lifetimes: FxIndexMap::default(),
s: self.scope,
next_early_index: self.next_early_index(),
track_lifetime_uses: true,
opaque_type_parent: false,
scope_type,
allow_late_bound: true,
@ -1510,7 +1475,6 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
lifetimes,
s: self.scope,
next_early_index,
track_lifetime_uses: true,
opaque_type_parent: false,
scope_type,
allow_late_bound: true,
@ -1812,7 +1776,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
where
F: for<'b> FnOnce(ScopeRef<'_>, &mut LifetimeContext<'b, 'tcx>),
{
let LifetimeContext { tcx, map, lifetime_uses, .. } = self;
let LifetimeContext { tcx, map, .. } = self;
let labels_in_fn = take(&mut self.labels_in_fn);
let xcrate_object_lifetime_defaults = take(&mut self.xcrate_object_lifetime_defaults);
let missing_named_lifetime_spots = take(&mut self.missing_named_lifetime_spots);
@ -1823,298 +1787,18 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
trait_definition_only: self.trait_definition_only,
labels_in_fn,
xcrate_object_lifetime_defaults,
lifetime_uses,
missing_named_lifetime_spots,
};
let span = tracing::debug_span!("scope", scope = ?TruncatedScopeDebug(&this.scope));
{
let _enter = span.enter();
f(self.scope, &mut this);
if !self.trait_definition_only {
this.check_uses_for_lifetimes_defined_by_scope();
}
}
self.labels_in_fn = this.labels_in_fn;
self.xcrate_object_lifetime_defaults = this.xcrate_object_lifetime_defaults;
self.missing_named_lifetime_spots = this.missing_named_lifetime_spots;
}
/// helper method to determine the span to remove when suggesting the
/// deletion of a lifetime
fn lifetime_deletion_span(&self, name: Ident, generics: &hir::Generics<'_>) -> Option<Span> {
generics.params.iter().enumerate().find_map(|(i, param)| {
if param.name.ident() == name {
if generics.params.len() == 1 {
// if sole lifetime, remove the entire `<>` brackets
Some(generics.span)
} else {
// if removing within `<>` brackets, we also want to
// delete a leading or trailing comma as appropriate
if i >= generics.params.len() - 1 {
Some(generics.params[i - 1].span.shrink_to_hi().to(param.span))
} else {
Some(param.span.to(generics.params[i + 1].span.shrink_to_lo()))
}
}
} else {
None
}
})
}
// helper method to issue suggestions from `fn rah<'a>(&'a T)` to `fn rah(&T)`
// or from `fn rah<'a>(T<'a>)` to `fn rah(T<'_>)`
fn suggest_eliding_single_use_lifetime(
&self,
err: &mut Diagnostic,
def_id: DefId,
lifetime: &hir::Lifetime,
) {
let name = lifetime.name.ident();
let remove_decl = self
.tcx
.parent(def_id)
.as_local()
.and_then(|parent_def_id| self.tcx.hir().get_generics(parent_def_id))
.and_then(|generics| self.lifetime_deletion_span(name, generics));
let mut remove_use = None;
let mut elide_use = None;
let mut find_arg_use_span = |inputs: &[hir::Ty<'_>]| {
for input in inputs {
match input.kind {
hir::TyKind::Rptr(lt, _) => {
if lt.name.ident() == name {
// include the trailing whitespace between the lifetime and type names
let lt_through_ty_span = lifetime.span.to(input.span.shrink_to_hi());
remove_use = Some(
self.tcx
.sess
.source_map()
.span_until_non_whitespace(lt_through_ty_span),
);
break;
}
}
hir::TyKind::Path(QPath::Resolved(_, path)) => {
let last_segment = &path.segments[path.segments.len() - 1];
let generics = last_segment.args();
for arg in generics.args.iter() {
if let GenericArg::Lifetime(lt) = arg {
if lt.name.ident() == name {
elide_use = Some(lt.span);
break;
}
}
}
break;
}
_ => {}
}
}
};
if let Node::Lifetime(hir_lifetime) = self.tcx.hir().get(lifetime.hir_id) {
if let Some(parent) =
self.tcx.hir().find_by_def_id(self.tcx.hir().get_parent_item(hir_lifetime.hir_id))
{
match parent {
Node::Item(item) => {
if let hir::ItemKind::Fn(sig, _, _) = &item.kind {
find_arg_use_span(sig.decl.inputs);
}
}
Node::ImplItem(impl_item) => {
if let hir::ImplItemKind::Fn(sig, _) = &impl_item.kind {
find_arg_use_span(sig.decl.inputs);
}
}
_ => {}
}
}
}
let msg = "elide the single-use lifetime";
match (remove_decl, remove_use, elide_use) {
(Some(decl_span), Some(use_span), None) => {
// if both declaration and use deletion spans start at the same
// place ("start at" because the latter includes trailing
// whitespace), then this is an in-band lifetime
if decl_span.shrink_to_lo() == use_span.shrink_to_lo() {
err.span_suggestion(
use_span,
msg,
String::new(),
Applicability::MachineApplicable,
);
} else {
err.multipart_suggestion(
msg,
vec![(decl_span, String::new()), (use_span, String::new())],
Applicability::MachineApplicable,
);
}
}
(Some(decl_span), None, Some(use_span)) => {
err.multipart_suggestion(
msg,
vec![(decl_span, String::new()), (use_span, "'_".to_owned())],
Applicability::MachineApplicable,
);
}
_ => {}
}
}
fn check_uses_for_lifetimes_defined_by_scope(&mut self) {
let Scope::Binder { lifetimes: defined_by, .. } = self.scope else {
debug!("check_uses_for_lifetimes_defined_by_scope: not in a binder scope");
return;
};
let def_ids: Vec<_> = defined_by
.values()
.flat_map(|region| match region {
Region::EarlyBound(_, def_id)
| Region::LateBound(_, _, def_id)
| Region::Free(_, def_id) => Some(*def_id),
Region::LateBoundAnon(..) | Region::Static => None,
})
.collect();
'lifetimes: for def_id in def_ids {
debug!("check_uses_for_lifetimes_defined_by_scope: def_id = {:?}", def_id);
let lifetimeuseset = self.lifetime_uses.remove(&def_id);
debug!(
"check_uses_for_lifetimes_defined_by_scope: lifetimeuseset = {:?}",
lifetimeuseset
);
match lifetimeuseset {
Some(LifetimeUseSet::One(lifetime)) => {
debug!(?def_id);
if let Some((id, span, name)) =
match self.tcx.hir().get_by_def_id(def_id.expect_local()) {
Node::Lifetime(hir_lifetime) => Some((
hir_lifetime.hir_id,
hir_lifetime.span,
hir_lifetime.name.ident(),
)),
Node::GenericParam(param) => {
Some((param.hir_id, param.span, param.name.ident()))
}
_ => None,
}
{
debug!("id = {:?} span = {:?} name = {:?}", id, span, name);
if name.name == kw::UnderscoreLifetime {
continue;
}
let parent_def_id = self.tcx.parent(def_id);
if let Some(def_id) = parent_def_id.as_local() {
// lifetimes in `derive` expansions don't count (Issue #53738)
if self.tcx.has_attr(def_id.to_def_id(), sym::automatically_derived) {
continue;
}
// opaque types generated when desugaring an async function can have a single
// use lifetime even if it is explicitly denied (Issue #77175)
if let hir::Node::Item(hir::Item {
kind: hir::ItemKind::OpaqueTy(ref opaque),
..
}) = self.tcx.hir().get_by_def_id(def_id)
{
if !matches!(opaque.origin, hir::OpaqueTyOrigin::AsyncFn(..)) {
continue 'lifetimes;
}
// We want to do this only if the lifetime identifier is already defined
// in the async function that generated this. Otherwise it could be
// an opaque type defined by the developer and we still want this
// lint to fail compilation
for p in opaque.generics.params {
if defined_by.contains_key(&p.name) {
continue 'lifetimes;
}
}
}
}
self.tcx.struct_span_lint_hir(
lint::builtin::SINGLE_USE_LIFETIMES,
id,
span,
|lint| {
let mut err = lint.build(&format!(
"lifetime parameter `{}` only used once",
name
));
if span == lifetime.span {
// spans are the same for in-band lifetime declarations
err.span_label(span, "this lifetime is only used here");
} else {
err.span_label(span, "this lifetime...");
err.span_label(lifetime.span, "...is used only here");
}
self.suggest_eliding_single_use_lifetime(
&mut err, def_id, lifetime,
);
err.emit();
},
);
}
}
Some(LifetimeUseSet::Many) => {
debug!("not one use lifetime");
}
None => {
if let Some((id, span, name)) =
match self.tcx.hir().get_by_def_id(def_id.expect_local()) {
Node::Lifetime(hir_lifetime) => Some((
hir_lifetime.hir_id,
hir_lifetime.span,
hir_lifetime.name.ident(),
)),
Node::GenericParam(param) => {
Some((param.hir_id, param.span, param.name.ident()))
}
_ => None,
}
{
debug!("id ={:?} span = {:?} name = {:?}", id, span, name);
self.tcx.struct_span_lint_hir(
lint::builtin::UNUSED_LIFETIMES,
id,
span,
|lint| {
let mut err = lint
.build(&format!("lifetime parameter `{}` never used", name));
let parent_def_id = self.tcx.parent(def_id);
if let Some(generics) =
self.tcx.hir().get_generics(parent_def_id.expect_local())
{
let unused_lt_span =
self.lifetime_deletion_span(name, generics);
if let Some(span) = unused_lt_span {
err.span_suggestion(
span,
"elide the unused lifetime",
String::new(),
Applicability::MachineApplicable,
);
}
}
err.emit();
},
);
}
}
}
}
}
/// Visits self by adding a scope and handling recursive walk over the contents with `walk`.
///
/// Handles visiting fns and methods. These are a bit complicated because we must distinguish
@ -2204,7 +1888,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
next_early_index,
s: self.scope,
opaque_type_parent: true,
track_lifetime_uses: false,
scope_type: BinderScopeType::Normal,
allow_late_bound: true,
};
@ -3201,41 +2884,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
}
}
/// Returns `true` if, in the current scope, replacing `'_` would be
/// equivalent to a single-use lifetime.
fn track_lifetime_uses(&self) -> bool {
let mut scope = self.scope;
loop {
match *scope {
Scope::Root => break false,
// Inside of items, it depends on the kind of item.
Scope::Binder { track_lifetime_uses, .. } => break track_lifetime_uses,
// Inside a body, `'_` will use an inference variable,
// should be fine.
Scope::Body { .. } => break true,
// A lifetime only used in a fn argument could as well
// be replaced with `'_`, as that would generate a
// fresh name, too.
Scope::Elision { elide: Elide::FreshLateAnon(..), .. } => break true,
// In the return type or other such place, `'_` is not
// going to make a fresh name, so we cannot
// necessarily replace a single-use lifetime with
// `'_`.
Scope::Elision {
elide: Elide::Exact(_) | Elide::Error(_) | Elide::Forbid, ..
} => break false,
Scope::ObjectLifetimeDefault { s, .. }
| Scope::Supertrait { s, .. }
| Scope::TraitRefBoundary { s, .. } => scope = s,
}
}
}
#[tracing::instrument(level = "debug", skip(self))]
fn insert_lifetime(&mut self, lifetime_ref: &'tcx hir::Lifetime, def: Region) {
debug!(
@ -3243,27 +2891,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
span = ?self.tcx.sess.source_map().span_to_diagnostic_string(lifetime_ref.span)
);
self.map.defs.insert(lifetime_ref.hir_id, def);
match def {
Region::LateBoundAnon(..) | Region::Static => {
// These are anonymous lifetimes or lifetimes that are not declared.
}
Region::Free(_, def_id)
| Region::LateBound(_, _, def_id)
| Region::EarlyBound(_, def_id) => {
// A lifetime declared by the user.
let track_lifetime_uses = self.track_lifetime_uses();
debug!(?track_lifetime_uses);
if track_lifetime_uses && !self.lifetime_uses.contains_key(&def_id) {
debug!("first use of {:?}", def_id);
self.lifetime_uses.insert(def_id, LifetimeUseSet::One(lifetime_ref));
} else {
debug!("many uses of {:?}", def_id);
self.lifetime_uses.insert(def_id, LifetimeUseSet::Many);
}
}
}
}
/// Sometimes we resolve a lifetime, but later find that it is an

View File

@ -1,19 +1,15 @@
// Check "unused_lifetimes" lint on both async and sync functions
// Both cases should be diagnosed the same way.
// edition:2018
#![deny(unused_lifetimes)]
async fn async_wrong_without_args<'a>() {} //~ ERROR lifetime parameter `'a` never used
// Async part with unused lifetimes
//
// Even wrong cases don't cause errors because async functions are desugared with all lifetimes
// involved in the signature. So, we cannot predict what lifetimes are unused in async function.
async fn async_wrong_without_args<'a>() {}
async fn async_wrong_1_lifetime<'a>(_: &i32) {} //~ ERROR lifetime parameter `'a` never used
async fn async_wrong_1_lifetime<'a>(_: &i32) {}
async fn async_wrong_2_lifetimes<'a, 'b>(_: &'a i32, _: &i32) {}
async fn async_wrong_2_lifetimes<'a, 'b>(_: &'a i32, _: &i32) {} //~ ERROR lifetime parameter `'b` never used
async fn async_right_1_lifetime<'a>(_: &'a i32) {}
@ -24,10 +20,6 @@ where
I: Iterator<Item = &'a i32>
{}
// Sync part with unused lifetimes
//
// These functions are compiled as supposed
fn wrong_without_args<'a>() {} //~ ERROR lifetime parameter `'a` never used
fn wrong_1_lifetime<'a>(_: &i32) {} //~ ERROR lifetime parameter `'a` never used

View File

@ -1,28 +1,48 @@
error: lifetime parameter `'a` never used
--> $DIR/unused-lifetime.rs:31:23
--> $DIR/unused-lifetime.rs:8:35
|
LL | fn wrong_without_args<'a>() {}
LL | async fn async_wrong_without_args<'a>() {}
| -^^- help: elide the unused lifetime
|
note: the lint level is defined here
--> $DIR/unused-lifetime.rs:5:9
--> $DIR/unused-lifetime.rs:6:9
|
LL | #![deny(unused_lifetimes)]
| ^^^^^^^^^^^^^^^^
error: lifetime parameter `'a` never used
--> $DIR/unused-lifetime.rs:33:21
--> $DIR/unused-lifetime.rs:10:33
|
LL | async fn async_wrong_1_lifetime<'a>(_: &i32) {}
| -^^- help: elide the unused lifetime
error: lifetime parameter `'b` never used
--> $DIR/unused-lifetime.rs:12:38
|
LL | async fn async_wrong_2_lifetimes<'a, 'b>(_: &'a i32, _: &i32) {}
| --^^
| |
| help: elide the unused lifetime
error: lifetime parameter `'a` never used
--> $DIR/unused-lifetime.rs:23:23
|
LL | fn wrong_without_args<'a>() {}
| -^^- help: elide the unused lifetime
error: lifetime parameter `'a` never used
--> $DIR/unused-lifetime.rs:25:21
|
LL | fn wrong_1_lifetime<'a>(_: &i32) {}
| -^^- help: elide the unused lifetime
error: lifetime parameter `'b` never used
--> $DIR/unused-lifetime.rs:35:26
--> $DIR/unused-lifetime.rs:27:26
|
LL | fn wrong_2_lifetimes<'a, 'b>(_: &'a i32, _: &i32) {}
| --^^
| |
| help: elide the unused lifetime
error: aborting due to 3 previous errors
error: aborting due to 6 previous errors

View File

@ -11,6 +11,11 @@ note: the lint level is defined here
|
LL | #![deny(single_use_lifetimes)]
| ^^^^^^^^^^^^^^^^^^^^
help: elide the single-use lifetime
|
LL - a: for<'a> fn(&'a u32),
LL + a: fn(&u32),
|
error[E0581]: return type references lifetime `'a`, which is not constrained by the fn input types
--> $DIR/fn-types.rs:12:22

View File

@ -19,4 +19,15 @@ fn left<'x, 'y>(foo: Double<'x, 'y>) -> &'x u32 { foo.f } //~ ERROR `'y` only us
fn right<'x, 'y>(foo: Double<'x, 'y>) -> &'y u32 { foo.f } //~ ERROR `'x` only used once
//~^ HELP elide the single-use lifetime
pub trait Tfv<'a> {}
// Do NOT lint in an HRTB.
pub fn g<T: for<'a> Tfv<'a>>() {}
// Do NOT lint for trait bounds.
pub fn h<'a, S>(_: S)
where
S: Tfv<'a>,
{}
fn main() {}

View File

@ -14,4 +14,10 @@ fn b<'a>() -> &'a u32 {
&22
}
pub trait Tfv<'a> {}
impl Tfv<'_> for () {}
// Do NOT lint if used in return type.
pub fn i<'a>() -> impl Tfv<'a> {}
fn main() {}

View File

@ -11,6 +11,11 @@ note: the lint level is defined here
|
LL | #![deny(single_use_lifetimes)]
| ^^^^^^^^^^^^^^^^^^^^
help: elide the single-use lifetime
|
LL - impl<'f> Foo<'f> {
LL + impl Foo<'_> {
|
error: aborting due to previous error

View File

@ -9,6 +9,7 @@ struct Foo<'f> {
}
impl<'f> Foo<'f> { //~ ERROR `'f` only used once
//~^ HELP elide the single-use lifetime
fn inherent_a<'a>(&self, data: &'a u32) { //~ ERROR `'a` only used once
//~^ HELP elide the single-use lifetime
}

View File

@ -1,7 +1,7 @@
error: lifetime parameter `'a` only used once
--> $DIR/one-use-in-inherent-method-argument.rs:12:19
error: lifetime parameter `'f` only used once
--> $DIR/one-use-in-inherent-method-argument.rs:11:6
|
LL | fn inherent_a<'a>(&self, data: &'a u32) {
LL | impl<'f> Foo<'f> {
| ^^ -- ...is used only here
| |
| this lifetime...
@ -13,17 +13,23 @@ LL | #![deny(single_use_lifetimes)]
| ^^^^^^^^^^^^^^^^^^^^
help: elide the single-use lifetime
|
LL - impl<'f> Foo<'f> {
LL + impl Foo<'_> {
|
error: lifetime parameter `'a` only used once
--> $DIR/one-use-in-inherent-method-argument.rs:13:19
|
LL | fn inherent_a<'a>(&self, data: &'a u32) {
| ^^ -- ...is used only here
| |
| this lifetime...
|
help: elide the single-use lifetime
|
LL - fn inherent_a<'a>(&self, data: &'a u32) {
LL + fn inherent_a(&self, data: &u32) {
|
error: lifetime parameter `'f` only used once
--> $DIR/one-use-in-inherent-method-argument.rs:11:6
|
LL | impl<'f> Foo<'f> {
| ^^ -- ...is used only here
| |
| this lifetime...
error: aborting due to 2 previous errors

View File

@ -11,6 +11,11 @@ note: the lint level is defined here
|
LL | #![deny(single_use_lifetimes)]
| ^^^^^^^^^^^^^^^^^^^^
help: elide the single-use lifetime
|
LL - impl<'f> Foo<'f> {
LL + impl Foo<'_> {
|
error: aborting due to previous error

View File

@ -4,7 +4,8 @@
//
// check-pass
#![deny(single_use_lifetimes)]
// Use forbid to verify that `automatically_derived` is handled correctly.
#![forbid(single_use_lifetimes)]
#![allow(dead_code)]
#![allow(unused_variables)]

View File

@ -18,4 +18,9 @@ impl<'f> Iterator for Foo<'f> {
}
}
trait Bar<'a> {
// But we should not warn here.
fn bar(x: Foo<'a>);
}
fn main() {}

View File

@ -11,6 +11,11 @@ note: the lint level is defined here
|
LL | #![deny(single_use_lifetimes)]
| ^^^^^^^^^^^^^^^^^^^^
help: elide the single-use lifetime
|
LL - impl<'f> Foo<'f> {
LL + impl Foo<'_> {
|
error: aborting due to previous error