Skip to content

Commit 151fae0

Browse files
committed
Add new HIR implementations of hidden lifetimes in paths lints
1 parent f0ee773 commit 151fae0

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
.note_glob_reexport = the name `{$name}` in the {$namespace} namespace is supposed to be publicly re-exported here
290290
.note_private_item = but the private item here shadows it
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_lifetime_parameters = hidden lifetime parameters in types are deprecated
293299
294300
lint_hidden_unicode_codepoints = unicode codepoint changing visible direction of text present in {$label}

compiler/rustc_lint/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ late_lint_methods!(
249249
UnqualifiedLocalImports: UnqualifiedLocalImports,
250250
CheckTransmutes: CheckTransmutes,
251251
LifetimeSyntax: LifetimeSyntax,
252+
HiddenLifetimesInTypePaths: HiddenLifetimesInTypePaths::default(),
252253
]
253254
]
254255
);

compiler/rustc_lint/src/lifetime_syntax.rs

Lines changed: 240 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, LifetimeSource};
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

@@ -71,7 +74,84 @@ declare_lint! {
7174
"detects when a lifetime uses different syntax between arguments and return values"
7275
}
7376

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

76156
impl<'tcx> LateLintPass<'tcx> for LifetimeSyntax {
77157
#[instrument(skip_all)]
@@ -96,6 +176,8 @@ impl<'tcx> LateLintPass<'tcx> for LifetimeSyntax {
96176
}
97177

98178
report_mismatches(cx, &input_map, &output_map);
179+
report_hidden_in_paths(cx, &input_map, HIDDEN_LIFETIMES_IN_INPUT_PATHS);
180+
report_hidden_in_paths(cx, &output_map, HIDDEN_LIFETIMES_IN_OUTPUT_PATHS);
99181
}
100182
}
101183

@@ -407,6 +489,50 @@ fn build_mismatch_suggestion(
407489
}
408490
}
409491

492+
fn report_hidden_in_paths<'tcx>(
493+
cx: &LateContext<'tcx>,
494+
info_map: &LifetimeInfoMap<'tcx>,
495+
lint: &'static Lint,
496+
) {
497+
let relevant_lifetimes = info_map
498+
.iter()
499+
.filter(|&(&&res, _)| reportable_lifetime_resolution(res))
500+
.flat_map(|(_, info)| info)
501+
.filter(|info| {
502+
matches!(info.lifetime.source, LifetimeSource::Path { .. })
503+
&& info.lifetime.is_implicit()
504+
});
505+
506+
let mut reporting_spans = Vec::new();
507+
let mut suggestions = Vec::new();
508+
509+
for info in relevant_lifetimes {
510+
reporting_spans.push(info.reporting_span());
511+
suggestions.push(info.suggestion("'_"));
512+
}
513+
514+
if reporting_spans.is_empty() {
515+
return;
516+
}
517+
518+
cx.emit_span_lint(
519+
lint,
520+
reporting_spans,
521+
lints::HiddenLifetimeInPath {
522+
suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions },
523+
},
524+
);
525+
}
526+
527+
/// We don't care about errors, nor do we care about the lifetime
528+
/// inside of a trait object.
529+
fn reportable_lifetime_resolution(kind: hir::LifetimeKind) -> bool {
530+
matches!(
531+
kind,
532+
hir::LifetimeKind::Param(..) | hir::LifetimeKind::Infer | hir::LifetimeKind::Static
533+
)
534+
}
535+
410536
#[derive(Debug)]
411537
struct Info<'tcx> {
412538
type_span: Span,
@@ -501,3 +627,115 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> {
501627
self.referenced_type_span = old_referenced_type_span;
502628
}
503629
}
630+
631+
declare_lint! {
632+
/// The `hidden_lifetimes_in_type_paths` lint detects the use of
633+
/// hidden lifetime parameters in types not part of a function's
634+
/// arguments or return values.
635+
///
636+
/// ### Example
637+
///
638+
/// ```rust,compile_fail
639+
/// #![deny(hidden_lifetimes_in_type_paths)]
640+
///
641+
/// struct ContainsLifetime<'a>(&'a i32);
642+
///
643+
/// static FOO: ContainsLifetime = ContainsLifetime(&42);
644+
/// ```
645+
///
646+
/// {{produces}}
647+
///
648+
/// ### Explanation
649+
///
650+
/// Hidden lifetime parameters can make it difficult to see at a
651+
/// glance that borrowing is occurring.
652+
///
653+
/// This lint ensures that lifetime parameters are always
654+
/// explicitly stated, even if it is the `'_` [placeholder
655+
/// lifetime].
656+
///
657+
/// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions
658+
pub HIDDEN_LIFETIMES_IN_TYPE_PATHS,
659+
Allow,
660+
"hidden lifetime parameters in types outside function signatures are discouraged"
661+
}
662+
663+
#[derive(Default)]
664+
pub(crate) struct HiddenLifetimesInTypePaths {
665+
inside_fn_signature: bool,
666+
}
667+
668+
impl_lint_pass!(HiddenLifetimesInTypePaths => [HIDDEN_LIFETIMES_IN_TYPE_PATHS]);
669+
670+
impl<'tcx> LateLintPass<'tcx> for HiddenLifetimesInTypePaths {
671+
#[instrument(skip(self, cx))]
672+
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
673+
if self.inside_fn_signature {
674+
return;
675+
}
676+
677+
// Do not lint about usages like `ContainsLifetime::method` or
678+
// `ContainsLifetimeAndType::<SomeType>::method`.
679+
if ty.source == hir::TySource::ImplicitSelf {
680+
return;
681+
}
682+
683+
let hir::TyKind::Path(path) = ty.kind else { return };
684+
685+
let path_segments = match path {
686+
hir::QPath::Resolved(_ty, path) => path.segments,
687+
688+
hir::QPath::TypeRelative(_ty, path_segment) => slice::from_ref(path_segment),
689+
690+
hir::QPath::LangItem(..) => &[],
691+
};
692+
693+
let mut suggestions = Vec::new();
694+
695+
for path_segment in path_segments {
696+
for arg in path_segment.args().args {
697+
if let hir::GenericArg::Lifetime(lifetime) = arg
698+
&& lifetime.is_implicit()
699+
&& reportable_lifetime_resolution(lifetime.kind)
700+
{
701+
suggestions.push(lifetime.suggestion("'_"))
702+
}
703+
}
704+
}
705+
706+
if suggestions.is_empty() {
707+
return;
708+
}
709+
710+
cx.emit_span_lint(
711+
HIDDEN_LIFETIMES_IN_TYPE_PATHS,
712+
ty.span,
713+
lints::HiddenLifetimeInPath {
714+
suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions },
715+
},
716+
);
717+
}
718+
719+
#[instrument(skip_all)]
720+
fn check_fn(
721+
&mut self,
722+
_: &LateContext<'tcx>,
723+
_: hir::intravisit::FnKind<'tcx>,
724+
_: &'tcx hir::FnDecl<'tcx>,
725+
_: &'tcx hir::Body<'tcx>,
726+
_: rustc_span::Span,
727+
_: rustc_span::def_id::LocalDefId,
728+
) {
729+
// We make the assumption that we will visit the function
730+
// declaration first, before visiting the body.
731+
self.inside_fn_signature = true;
732+
}
733+
734+
// This may be a function's body, which would indicate that we are
735+
// no longer in the signature. Even if it's not, a body cannot
736+
// occur inside a function signature.
737+
#[instrument(skip_all)]
738+
fn check_body(&mut self, _: &LateContext<'tcx>, _: &hir::Body<'tcx>) {
739+
self.inside_fn_signature = false;
740+
}
741+
}

compiler/rustc_lint/src/lints.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3314,3 +3314,24 @@ impl Subdiagnostic for MismatchedLifetimeSyntaxesSuggestion {
33143314
}
33153315
}
33163316
}
3317+
3318+
#[derive(LintDiagnostic)]
3319+
#[diag(lint_hidden_lifetime_in_path)]
3320+
pub(crate) struct HiddenLifetimeInPath {
3321+
#[subdiagnostic]
3322+
pub suggestions: HiddenLifetimeInPathSuggestion,
3323+
}
3324+
3325+
pub(crate) struct HiddenLifetimeInPathSuggestion {
3326+
pub suggestions: Vec<(Span, String)>,
3327+
}
3328+
3329+
impl Subdiagnostic for HiddenLifetimeInPathSuggestion {
3330+
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
3331+
diag.multipart_suggestion_verbose(
3332+
fluent::lint_hidden_lifetime_in_path_suggestion,
3333+
self.suggestions,
3334+
Applicability::MachineApplicable,
3335+
);
3336+
}
3337+
}

0 commit comments

Comments
 (0)