Skip to content

Commit 788db3e

Browse files
committed
Add new HIR implementations of hidden lifetimes in paths lints
1 parent 1bfae6e commit 788db3e

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
@@ -286,6 +286,12 @@ lint_hidden_glob_reexport = private item shadows public glob re-export
286286
.note_glob_reexport = the name `{$name}` in the {$namespace} namespace is supposed to be publicly re-exported here
287287
.note_private_item = but the private item here shadows it
288288
289+
lint_hidden_lifetime_in_path =
290+
paths containing hidden lifetime parameters are deprecated
291+
292+
lint_hidden_lifetime_in_path_suggestion =
293+
indicate the anonymous lifetime
294+
289295
lint_hidden_lifetime_parameters = hidden lifetime parameters in types are deprecated
290296
291297
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
@@ -248,6 +248,7 @@ late_lint_methods!(
248248
UnqualifiedLocalImports: UnqualifiedLocalImports,
249249
CheckTransmutes: CheckTransmutes,
250250
LifetimeSyntax: LifetimeSyntax,
251+
HiddenLifetimesInTypePaths: HiddenLifetimesInTypePaths::default(),
251252
]
252253
]
253254
);

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)]
@@ -123,6 +203,8 @@ fn check_fn_like<'tcx>(cx: &LateContext<'tcx>, fd: &'tcx hir::FnDecl<'tcx>) {
123203
}
124204

125205
report_mismatches(cx, &input_map, &output_map);
206+
report_hidden_in_paths(cx, &input_map, HIDDEN_LIFETIMES_IN_INPUT_PATHS);
207+
report_hidden_in_paths(cx, &output_map, HIDDEN_LIFETIMES_IN_OUTPUT_PATHS);
126208
}
127209

128210
#[instrument(skip_all)]
@@ -433,6 +515,50 @@ fn build_mismatch_suggestion(
433515
}
434516
}
435517

518+
fn report_hidden_in_paths<'tcx>(
519+
cx: &LateContext<'tcx>,
520+
info_map: &LifetimeInfoMap<'tcx>,
521+
lint: &'static Lint,
522+
) {
523+
let relevant_lifetimes = info_map
524+
.iter()
525+
.filter(|&(&&res, _)| reportable_lifetime_resolution(res))
526+
.flat_map(|(_, info)| info)
527+
.filter(|info| {
528+
matches!(info.lifetime.source, LifetimeSource::Path { .. })
529+
&& info.lifetime.is_implicit()
530+
});
531+
532+
let mut reporting_spans = Vec::new();
533+
let mut suggestions = Vec::new();
534+
535+
for info in relevant_lifetimes {
536+
reporting_spans.push(info.reporting_span());
537+
suggestions.push(info.suggestion("'_"));
538+
}
539+
540+
if reporting_spans.is_empty() {
541+
return;
542+
}
543+
544+
cx.emit_span_lint(
545+
lint,
546+
reporting_spans,
547+
lints::HiddenLifetimeInPath {
548+
suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions },
549+
},
550+
);
551+
}
552+
553+
/// We don't care about errors, nor do we care about the lifetime
554+
/// inside of a trait object.
555+
fn reportable_lifetime_resolution(kind: hir::LifetimeKind) -> bool {
556+
matches!(
557+
kind,
558+
hir::LifetimeKind::Param(..) | hir::LifetimeKind::Infer | hir::LifetimeKind::Static
559+
)
560+
}
561+
436562
#[derive(Debug)]
437563
struct Info<'tcx> {
438564
type_span: Span,
@@ -527,3 +653,115 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> {
527653
self.referenced_type_span = old_referenced_type_span;
528654
}
529655
}
656+
657+
declare_lint! {
658+
/// The `hidden_lifetimes_in_type_paths` lint detects the use of
659+
/// hidden lifetime parameters in types not part of a function's
660+
/// arguments or return values.
661+
///
662+
/// ### Example
663+
///
664+
/// ```rust,compile_fail
665+
/// #![deny(hidden_lifetimes_in_type_paths)]
666+
///
667+
/// struct ContainsLifetime<'a>(&'a i32);
668+
///
669+
/// static FOO: ContainsLifetime = ContainsLifetime(&42);
670+
/// ```
671+
///
672+
/// {{produces}}
673+
///
674+
/// ### Explanation
675+
///
676+
/// Hidden lifetime parameters can make it difficult to see at a
677+
/// glance that borrowing is occurring.
678+
///
679+
/// This lint ensures that lifetime parameters are always
680+
/// explicitly stated, even if it is the `'_` [placeholder
681+
/// lifetime].
682+
///
683+
/// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions
684+
pub HIDDEN_LIFETIMES_IN_TYPE_PATHS,
685+
Allow,
686+
"hidden lifetime parameters in types outside function signatures are discouraged"
687+
}
688+
689+
#[derive(Default)]
690+
pub(crate) struct HiddenLifetimesInTypePaths {
691+
inside_fn_signature: bool,
692+
}
693+
694+
impl_lint_pass!(HiddenLifetimesInTypePaths => [HIDDEN_LIFETIMES_IN_TYPE_PATHS]);
695+
696+
impl<'tcx> LateLintPass<'tcx> for HiddenLifetimesInTypePaths {
697+
#[instrument(skip(self, cx))]
698+
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
699+
if self.inside_fn_signature {
700+
return;
701+
}
702+
703+
// Do not lint about usages like `ContainsLifetime::method` or
704+
// `ContainsLifetimeAndType::<SomeType>::method`.
705+
if ty.source == hir::TySource::ImplicitSelf {
706+
return;
707+
}
708+
709+
let hir::TyKind::Path(path) = ty.kind else { return };
710+
711+
let path_segments = match path {
712+
hir::QPath::Resolved(_ty, path) => path.segments,
713+
714+
hir::QPath::TypeRelative(_ty, path_segment) => slice::from_ref(path_segment),
715+
716+
hir::QPath::LangItem(..) => &[],
717+
};
718+
719+
let mut suggestions = Vec::new();
720+
721+
for path_segment in path_segments {
722+
for arg in path_segment.args().args {
723+
if let hir::GenericArg::Lifetime(lifetime) = arg
724+
&& lifetime.is_implicit()
725+
&& reportable_lifetime_resolution(lifetime.kind)
726+
{
727+
suggestions.push(lifetime.suggestion("'_"))
728+
}
729+
}
730+
}
731+
732+
if suggestions.is_empty() {
733+
return;
734+
}
735+
736+
cx.emit_span_lint(
737+
HIDDEN_LIFETIMES_IN_TYPE_PATHS,
738+
ty.span,
739+
lints::HiddenLifetimeInPath {
740+
suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions },
741+
},
742+
);
743+
}
744+
745+
#[instrument(skip_all)]
746+
fn check_fn(
747+
&mut self,
748+
_: &LateContext<'tcx>,
749+
_: hir::intravisit::FnKind<'tcx>,
750+
_: &'tcx hir::FnDecl<'tcx>,
751+
_: &'tcx hir::Body<'tcx>,
752+
_: rustc_span::Span,
753+
_: rustc_span::def_id::LocalDefId,
754+
) {
755+
// We make the assumption that we will visit the function
756+
// declaration first, before visiting the body.
757+
self.inside_fn_signature = true;
758+
}
759+
760+
// This may be a function's body, which would indicate that we are
761+
// no longer in the signature. Even if it's not, a body cannot
762+
// occur inside a function signature.
763+
#[instrument(skip_all)]
764+
fn check_body(&mut self, _: &LateContext<'tcx>, _: &hir::Body<'tcx>) {
765+
self.inside_fn_signature = false;
766+
}
767+
}

compiler/rustc_lint/src/lints.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3284,3 +3284,24 @@ impl Subdiagnostic for MismatchedLifetimeSyntaxesSuggestion {
32843284
}
32853285
}
32863286
}
3287+
3288+
#[derive(LintDiagnostic)]
3289+
#[diag(lint_hidden_lifetime_in_path)]
3290+
pub(crate) struct HiddenLifetimeInPath {
3291+
#[subdiagnostic]
3292+
pub suggestions: HiddenLifetimeInPathSuggestion,
3293+
}
3294+
3295+
pub(crate) struct HiddenLifetimeInPathSuggestion {
3296+
pub suggestions: Vec<(Span, String)>,
3297+
}
3298+
3299+
impl Subdiagnostic for HiddenLifetimeInPathSuggestion {
3300+
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
3301+
diag.multipart_suggestion_verbose(
3302+
fluent::lint_hidden_lifetime_in_path_suggestion,
3303+
self.suggestions,
3304+
Applicability::MachineApplicable,
3305+
);
3306+
}
3307+
}

0 commit comments

Comments
 (0)