Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0c62f45

Browse files
committedMar 26, 2025
Add new HIR implementations of hidden lifetimes in paths lints
1 parent 1283a5a commit 0c62f45

File tree

4 files changed

+265
-2
lines changed

4 files changed

+265
-2
lines changed
 

Diff for: ‎compiler/rustc_lint/messages.ftl

+6
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,12 @@ lint_hidden_glob_reexport = private item shadows public glob re-export
287287
.note_glob_reexport = the name `{$name}` in the {$namespace} namespace is supposed to be publicly re-exported here
288288
.note_private_item = but the private item here shadows it
289289
290+
lint_hidden_lifetime_in_path =
291+
paths containing hidden lifetime parameters are deprecated
292+
293+
lint_hidden_lifetime_in_path_suggestion =
294+
indicate the anonymous lifetime
295+
290296
lint_hidden_lifetime_parameters = hidden lifetime parameters in types are deprecated
291297
292298
lint_hidden_unicode_codepoints = unicode codepoint changing visible direction of text present in {$label}

Diff for: ‎compiler/rustc_lint/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ late_lint_methods!(
247247
StaticMutRefs: StaticMutRefs,
248248
UnqualifiedLocalImports: UnqualifiedLocalImports,
249249
LifetimeStyle: LifetimeStyle,
250+
HiddenLifetimesInTypePaths: HiddenLifetimesInTypePaths::default(),
250251
]
251252
]
252253
);

Diff for: ‎compiler/rustc_lint/src/lifetime_style.rs

+237-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
use std::slice;
2+
13
use rustc_data_structures::fx::FxIndexMap;
24
use rustc_hir::intravisit::{self, Visitor};
35
use rustc_hir::{self as hir};
4-
use rustc_session::{declare_lint, declare_lint_pass};
6+
use rustc_session::lint::Lint;
7+
use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass};
58
use rustc_span::Span;
69
use tracing::instrument;
710

@@ -66,7 +69,84 @@ declare_lint! {
6669
"detects when an elided lifetime uses different syntax between arguments and return values"
6770
}
6871

69-
declare_lint_pass!(LifetimeStyle => [MISMATCHED_LIFETIME_SYNTAXES]);
72+
declare_lint! {
73+
/// The `hidden_lifetimes_in_input_paths2` lint detects the use of
74+
/// hidden lifetime parameters in types occurring as a function
75+
/// argument.
76+
///
77+
/// ### Example
78+
///
79+
/// ```rust,compile_fail
80+
/// #![deny(hidden_lifetimes_in_input_paths2)]
81+
///
82+
/// struct ContainsLifetime<'a>(&'a i32);
83+
///
84+
/// fn foo(x: &ContainsLifetime) {}
85+
/// ```
86+
///
87+
/// {{produces}}
88+
///
89+
/// ### Explanation
90+
///
91+
/// Hidden lifetime parameters can make it difficult to see at a
92+
/// glance that borrowing is occurring.
93+
///
94+
/// This lint ensures that lifetime parameters are always
95+
/// explicitly stated, even if it is the `'_` [placeholder
96+
/// lifetime].
97+
///
98+
/// This lint is "allow" by default as function arguments by
99+
/// themselves do not usually cause much confusion.
100+
///
101+
/// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions
102+
pub HIDDEN_LIFETIMES_IN_INPUT_PATHS2,
103+
Allow,
104+
"hidden lifetime parameters in types in function arguments may be confusing"
105+
}
106+
107+
declare_lint! {
108+
/// The `hidden_lifetimes_in_output_paths2` lint detects the use
109+
/// of hidden lifetime parameters in types occurring as a function
110+
/// return value.
111+
///
112+
/// ### Example
113+
///
114+
/// ```rust,compile_fail
115+
/// #![deny(hidden_lifetimes_in_input_paths2)]
116+
///
117+
/// struct ContainsLifetime<'a>(&'a i32);
118+
///
119+
/// fn foo(x: &i32) -> ContainsLifetime {
120+
/// ContainsLifetime(x)
121+
/// }
122+
/// ```
123+
///
124+
/// {{produces}}
125+
///
126+
/// ### Explanation
127+
///
128+
/// Hidden lifetime parameters can make it difficult to see at a
129+
/// glance that borrowing is occurring. This is especially true
130+
/// when a type is used as a function's return value: lifetime
131+
/// elision will link the return value's lifetime to an argument's
132+
/// lifetime, but no syntax in the function signature indicates
133+
/// that.
134+
///
135+
/// This lint ensures that lifetime parameters are always
136+
/// explicitly stated, even if it is the `'_` [placeholder
137+
/// lifetime].
138+
///
139+
/// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions
140+
pub HIDDEN_LIFETIMES_IN_OUTPUT_PATHS2,
141+
Allow,
142+
"hidden lifetime parameters in types in function return values are deprecated"
143+
}
144+
145+
declare_lint_pass!(LifetimeStyle => [
146+
MISMATCHED_LIFETIME_SYNTAXES,
147+
HIDDEN_LIFETIMES_IN_INPUT_PATHS2,
148+
HIDDEN_LIFETIMES_IN_OUTPUT_PATHS2,
149+
]);
70150

71151
impl<'tcx> LateLintPass<'tcx> for LifetimeStyle {
72152
#[instrument(skip_all)]
@@ -91,6 +171,8 @@ impl<'tcx> LateLintPass<'tcx> for LifetimeStyle {
91171
}
92172

93173
report_mismatches(cx, &input_map, &output_map);
174+
report_hidden_in_paths(cx, &input_map, HIDDEN_LIFETIMES_IN_INPUT_PATHS2);
175+
report_hidden_in_paths(cx, &output_map, HIDDEN_LIFETIMES_IN_OUTPUT_PATHS2);
94176
}
95177
}
96178

@@ -231,6 +313,47 @@ fn build_mismatch_suggestion(
231313
}
232314
}
233315

316+
fn report_hidden_in_paths<'tcx>(
317+
cx: &LateContext<'tcx>,
318+
info_map: &LifetimeInfoMap<'tcx>,
319+
lint: &'static Lint,
320+
) {
321+
let relevant_lifetimes = info_map
322+
.iter()
323+
.filter(|&(&&res, _)| reportable_lifetime_resolution(res))
324+
.flat_map(|(_, info)| info)
325+
.filter(|info| info.source == LifetimeSource::Path && info.syntax == SyntaxKind::Hidden);
326+
327+
let mut reporting_spans = Vec::new();
328+
let mut suggestions = Vec::new();
329+
330+
for info in relevant_lifetimes {
331+
reporting_spans.push(info.reporting_span());
332+
suggestions.push(info.suggestion("'_"));
333+
}
334+
335+
if reporting_spans.is_empty() {
336+
return;
337+
}
338+
339+
cx.emit_span_lint(
340+
lint,
341+
reporting_spans,
342+
lints::HiddenLifetimeInPath {
343+
suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions },
344+
},
345+
);
346+
}
347+
348+
/// We don't care about errors, nor do we care about the lifetime
349+
/// inside of a trait object.
350+
fn reportable_lifetime_resolution(res: hir::LifetimeName) -> bool {
351+
matches!(
352+
res,
353+
hir::LifetimeName::Param(..) | hir::LifetimeName::Infer | hir::LifetimeName::Static
354+
)
355+
}
356+
234357
#[derive(Debug, Copy, Clone, PartialEq)]
235358
enum LifetimeSource {
236359
Reference,
@@ -329,3 +452,115 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> {
329452
self.is_ref = old_is_ref;
330453
}
331454
}
455+
456+
declare_lint! {
457+
/// The `hidden_lifetimes_in_type_paths2` lint detects the use of
458+
/// hidden lifetime parameters in types not part of a function's
459+
/// arguments or return values.
460+
///
461+
/// ### Example
462+
///
463+
/// ```rust,compile_fail
464+
/// #![deny(hidden_lifetimes_in_input_paths2)]
465+
///
466+
/// struct ContainsLifetime<'a>(&'a i32);
467+
///
468+
/// static FOO: ContainsLifetime = ContainsLifetime(&42);
469+
/// ```
470+
///
471+
/// {{produces}}
472+
///
473+
/// ### Explanation
474+
///
475+
/// Hidden lifetime parameters can make it difficult to see at a
476+
/// glance that borrowing is occurring.
477+
///
478+
/// This lint ensures that lifetime parameters are always
479+
/// explicitly stated, even if it is the `'_` [placeholder
480+
/// lifetime].
481+
///
482+
/// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions
483+
pub HIDDEN_LIFETIMES_IN_TYPE_PATHS2,
484+
Allow,
485+
"hidden lifetime parameters in types outside function signatures are discouraged"
486+
}
487+
488+
#[derive(Default)]
489+
pub(crate) struct HiddenLifetimesInTypePaths {
490+
inside_fn_signature: bool,
491+
}
492+
493+
impl_lint_pass!(HiddenLifetimesInTypePaths => [HIDDEN_LIFETIMES_IN_TYPE_PATHS2]);
494+
495+
impl<'tcx> LateLintPass<'tcx> for HiddenLifetimesInTypePaths {
496+
#[instrument(skip(self, cx))]
497+
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
498+
if self.inside_fn_signature {
499+
return;
500+
}
501+
502+
// Do not lint about usages like `ContainsLifetime::method` or
503+
// `ContainsLifetimeAndType::<SomeType>::method`.
504+
if ty.source == hir::TySource::ImplicitSelf {
505+
return;
506+
}
507+
508+
let hir::TyKind::Path(path) = ty.kind else { return };
509+
510+
let path_segments = match path {
511+
hir::QPath::Resolved(_ty, path) => path.segments,
512+
513+
hir::QPath::TypeRelative(_ty, path_segment) => slice::from_ref(path_segment),
514+
515+
hir::QPath::LangItem(..) => &[],
516+
};
517+
518+
let mut suggestions = Vec::new();
519+
520+
for path_segment in path_segments {
521+
for arg in path_segment.args().args {
522+
if let hir::GenericArg::Lifetime(lifetime) = arg
523+
&& lifetime.is_syntactically_hidden()
524+
&& reportable_lifetime_resolution(lifetime.res)
525+
{
526+
suggestions.push(lifetime.suggestion("'_", false))
527+
}
528+
}
529+
}
530+
531+
if suggestions.is_empty() {
532+
return;
533+
}
534+
535+
cx.emit_span_lint(
536+
HIDDEN_LIFETIMES_IN_TYPE_PATHS2,
537+
ty.span,
538+
lints::HiddenLifetimeInPath {
539+
suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions },
540+
},
541+
);
542+
}
543+
544+
#[instrument(skip_all)]
545+
fn check_fn(
546+
&mut self,
547+
_: &LateContext<'tcx>,
548+
_: hir::intravisit::FnKind<'tcx>,
549+
_: &'tcx hir::FnDecl<'tcx>,
550+
_: &'tcx hir::Body<'tcx>,
551+
_: rustc_span::Span,
552+
_: rustc_span::def_id::LocalDefId,
553+
) {
554+
// We make the assumption that we will visit the function
555+
// declaration first, before visiting the body.
556+
self.inside_fn_signature = true;
557+
}
558+
559+
// This may be a function's body, which would indicate that we are
560+
// no longer in the signature. Even if it's not, a body cannot
561+
// occur inside a function signature.
562+
#[instrument(skip_all)]
563+
fn check_body(&mut self, _: &LateContext<'tcx>, _: &hir::Body<'tcx>) {
564+
self.inside_fn_signature = false;
565+
}
566+
}

Diff for: ‎compiler/rustc_lint/src/lints.rs

+21
Original file line numberDiff line numberDiff line change
@@ -3175,3 +3175,24 @@ impl Subdiagnostic for MismatchedLifetimeSyntaxesSuggestion {
31753175
}
31763176
}
31773177
}
3178+
3179+
#[derive(LintDiagnostic)]
3180+
#[diag(lint_hidden_lifetime_in_path)]
3181+
pub(crate) struct HiddenLifetimeInPath {
3182+
#[subdiagnostic]
3183+
pub suggestions: HiddenLifetimeInPathSuggestion,
3184+
}
3185+
3186+
pub(crate) struct HiddenLifetimeInPathSuggestion {
3187+
pub suggestions: Vec<(Span, String)>,
3188+
}
3189+
3190+
impl Subdiagnostic for HiddenLifetimeInPathSuggestion {
3191+
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
3192+
diag.multipart_suggestion_verbose(
3193+
fluent::lint_hidden_lifetime_in_path_suggestion,
3194+
self.suggestions,
3195+
Applicability::MachineApplicable,
3196+
);
3197+
}
3198+
}

0 commit comments

Comments
 (0)
Please sign in to comment.