Skip to content

Commit 54c9d04

Browse files
committed
Add new HIR implementations of hidden lifetimes in paths lints
1 parent 2821fce commit 54c9d04

File tree

4 files changed

+268
-2
lines changed

4 files changed

+268
-2
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,12 @@ lint_hidden_glob_reexport = private item shadows public glob re-export
289289
290290
lint_hidden_lifetime_parameters = hidden lifetime parameters in types are deprecated
291291
292+
lint_hidden_lifetime_in_path =
293+
paths containing hidden lifetime parameters are deprecated
294+
295+
lint_hidden_lifetime_in_path_suggestion =
296+
indicate the anonymous lifetime
297+
292298
lint_hidden_unicode_codepoints = unicode codepoint changing visible direction of text present in {$label}
293299
.label = this {$label} contains {$count ->
294300
[one] an invisible

compiler/rustc_lint/src/lib.rs

Lines changed: 1 addition & 0 deletions
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
);

compiler/rustc_lint/src/lifetime_style.rs

Lines changed: 236 additions & 2 deletions
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

@@ -60,7 +63,84 @@ declare_lint! {
6063
"detects when an elided lifetime uses different syntax between arguments and return values"
6164
}
6265

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

65145
impl<'tcx> LateLintPass<'tcx> for LifetimeStyle {
66146
#[instrument(skip_all)]
@@ -85,6 +165,8 @@ impl<'tcx> LateLintPass<'tcx> for LifetimeStyle {
85165
}
86166

87167
report_mismatches(cx, &input_map, &output_map);
168+
report_hidden_in_paths(cx, &input_map, HIDDEN_LIFETIMES_IN_INPUT_PATHS2);
169+
report_hidden_in_paths(cx, &output_map, HIDDEN_LIFETIMES_IN_OUTPUT_PATHS2);
88170
}
89171
}
90172

@@ -231,6 +313,47 @@ fn build_mismatch_suggestion(
231313
MismatchedElidedLifetimeStylesSuggestion::Named { lifetime_name, suggestions, tool_only: false }
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.style == SyntaxStyle::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,
@@ -326,3 +449,114 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> {
326449
self.is_ref = old_is_ref;
327450
}
328451
}
452+
453+
declare_lint! {
454+
/// The `hidden_lifetimes_in_type_paths2` lint detects the use of
455+
/// hidden lifetime parameters in types not part of a function's
456+
/// arguments or return values.
457+
///
458+
/// ### Example
459+
///
460+
/// ```rust,compile_fail
461+
/// #![deny(hidden_lifetimes_in_input_paths2)]
462+
///
463+
/// struct ContainsLifetime<'a>(&'a i32);
464+
///
465+
/// static FOO: ContainsLifetime = ContainsLifetime(&42);
466+
/// ```
467+
///
468+
/// {{produces}}
469+
///
470+
/// ### Explanation
471+
///
472+
/// Hidden lifetime parameters can make it difficult to see at a
473+
/// glance that borrowing is occurring.
474+
///
475+
/// This lint ensures that lifetime parameters are always
476+
/// explicitly stated, even if it is the `'_` [placeholder
477+
/// lifetime].
478+
///
479+
/// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions
480+
pub HIDDEN_LIFETIMES_IN_TYPE_PATHS2,
481+
Allow,
482+
"hidden lifetime parameters in types outside function signatures are discouraged"
483+
}
484+
485+
#[derive(Default)]
486+
pub(crate) struct HiddenLifetimesInTypePaths {
487+
inside_fn_signature: bool,
488+
}
489+
490+
impl_lint_pass!(HiddenLifetimesInTypePaths => [HIDDEN_LIFETIMES_IN_TYPE_PATHS2]);
491+
492+
impl<'tcx> LateLintPass<'tcx> for HiddenLifetimesInTypePaths {
493+
#[instrument(skip(self, cx))]
494+
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
495+
if self.inside_fn_signature {
496+
return;
497+
}
498+
499+
let hir::TyKind::Path(path) = ty.kind else { return };
500+
501+
let path_segments = match path {
502+
hir::QPath::Resolved(_ty, path) => path.segments,
503+
504+
hir::QPath::TypeRelative(_ty, path_segment) => slice::from_ref(path_segment),
505+
506+
hir::QPath::LangItem(..) => &[],
507+
};
508+
509+
let mut suggestions = Vec::new();
510+
511+
for path_segment in path_segments {
512+
// Do not lint about usages like `ContainsLifetime::method`.
513+
if path_segment.infer_args {
514+
continue;
515+
}
516+
517+
for arg in path_segment.args().args {
518+
if let hir::GenericArg::Lifetime(lifetime) = arg
519+
&& lifetime.is_syntactically_hidden()
520+
&& reportable_lifetime_resolution(lifetime.res)
521+
{
522+
suggestions.push(lifetime.suggestion("'_", false))
523+
}
524+
}
525+
}
526+
527+
if suggestions.is_empty() {
528+
return;
529+
}
530+
531+
cx.emit_span_lint(
532+
HIDDEN_LIFETIMES_IN_TYPE_PATHS2,
533+
ty.span,
534+
lints::HiddenLifetimeInPath {
535+
suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions },
536+
},
537+
);
538+
}
539+
540+
#[instrument(skip_all)]
541+
fn check_fn(
542+
&mut self,
543+
_: &LateContext<'tcx>,
544+
_: hir::intravisit::FnKind<'tcx>,
545+
_: &'tcx hir::FnDecl<'tcx>,
546+
_: &'tcx hir::Body<'tcx>,
547+
_: rustc_span::Span,
548+
_: rustc_span::def_id::LocalDefId,
549+
) {
550+
// We make the assumption that we will visit the function
551+
// declaration first, before visiting the body.
552+
self.inside_fn_signature = true;
553+
}
554+
555+
// This may be a function's body, which would indicate that we are
556+
// no longer in the signature. Even if it's not, a body cannot
557+
// occur inside a function signature.
558+
#[instrument(skip_all)]
559+
fn check_body(&mut self, _: &LateContext<'tcx>, _: &hir::Body<'tcx>) {
560+
self.inside_fn_signature = false;
561+
}
562+
}

compiler/rustc_lint/src/lints.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3206,3 +3206,28 @@ impl Subdiagnostic for MismatchedElidedLifetimeStylesSuggestion {
32063206
}
32073207
}
32083208
}
3209+
3210+
#[derive(LintDiagnostic)]
3211+
#[diag(lint_hidden_lifetime_in_path)]
3212+
pub(crate) struct HiddenLifetimeInPath {
3213+
#[subdiagnostic]
3214+
pub suggestions: HiddenLifetimeInPathSuggestion,
3215+
}
3216+
3217+
pub(crate) struct HiddenLifetimeInPathSuggestion {
3218+
pub suggestions: Vec<(Span, String)>,
3219+
}
3220+
3221+
impl Subdiagnostic for HiddenLifetimeInPathSuggestion {
3222+
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
3223+
self,
3224+
diag: &mut Diag<'_, G>,
3225+
_f: &F,
3226+
) {
3227+
diag.multipart_suggestion_verbose(
3228+
fluent::lint_hidden_lifetime_in_path_suggestion,
3229+
self.suggestions,
3230+
Applicability::MachineApplicable,
3231+
);
3232+
}
3233+
}

0 commit comments

Comments
 (0)