Skip to content

Commit 7957d6d

Browse files
committed
Add new HIR implementations of hidden lifetimes in paths lints
1 parent 51f875f commit 7957d6d

File tree

4 files changed

+268
-2
lines changed

4 files changed

+268
-2
lines changed

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}

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
);

compiler/rustc_lint/src/lifetime_style.rs

+240-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, LifetimeSource, LifetimeSyntax};
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_output_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

@@ -233,6 +315,50 @@ fn build_mismatch_suggestion(
233315
}
234316
}
235317

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

compiler/rustc_lint/src/lints.rs

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

0 commit comments

Comments
 (0)