Skip to content

Commit bf80f89

Browse files
committed
Add new HIR implementations of hidden lifetimes in paths lints
1 parent 413c5f2 commit bf80f89

File tree

4 files changed

+271
-2
lines changed

4 files changed

+271
-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: 243 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)]
@@ -512,6 +594,50 @@ fn build_mismatch_suggestion(
512594
}
513595
}
514596

597+
fn report_hidden_in_paths<'tcx>(
598+
cx: &LateContext<'tcx>,
599+
info_map: &LifetimeInfoMap<'tcx>,
600+
lint: &'static Lint,
601+
) {
602+
let relevant_lifetimes = info_map
603+
.iter()
604+
.filter(|&(&&res, _)| reportable_lifetime_resolution(res))
605+
.flat_map(|(_, info)| info)
606+
.filter(|info| {
607+
matches!(info.lifetime.source, LifetimeSource::Path { .. })
608+
&& info.lifetime.is_implicit()
609+
});
610+
611+
let mut reporting_spans = Vec::new();
612+
let mut suggestions = Vec::new();
613+
614+
for info in relevant_lifetimes {
615+
reporting_spans.push(info.reporting_span());
616+
suggestions.push(info.suggestion("'_"));
617+
}
618+
619+
if reporting_spans.is_empty() {
620+
return;
621+
}
622+
623+
cx.emit_span_lint(
624+
lint,
625+
reporting_spans,
626+
lints::HiddenLifetimeInPath {
627+
suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions },
628+
},
629+
);
630+
}
631+
632+
/// We don't care about errors, nor do we care about the lifetime
633+
/// inside of a trait object.
634+
fn reportable_lifetime_resolution(kind: hir::LifetimeKind) -> bool {
635+
matches!(
636+
kind,
637+
hir::LifetimeKind::Param(..) | hir::LifetimeKind::Infer | hir::LifetimeKind::Static
638+
)
639+
}
640+
515641
#[derive(Debug)]
516642
struct Info<'tcx> {
517643
type_span: Span,
@@ -614,3 +740,118 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> {
614740
self.referenced_type_span = old_referenced_type_span;
615741
}
616742
}
743+
744+
declare_lint! {
745+
/// The `hidden_lifetimes_in_type_paths` lint detects the use of
746+
/// hidden lifetime parameters in types not part of a function's
747+
/// arguments or return values.
748+
///
749+
/// ### Example
750+
///
751+
/// ```rust,compile_fail
752+
/// #![deny(hidden_lifetimes_in_type_paths)]
753+
///
754+
/// struct ContainsLifetime<'a>(&'a i32);
755+
///
756+
/// static FOO: ContainsLifetime = ContainsLifetime(&42);
757+
/// ```
758+
///
759+
/// {{produces}}
760+
///
761+
/// ### Explanation
762+
///
763+
/// Hidden lifetime parameters can make it difficult to see at a
764+
/// glance that borrowing is occurring.
765+
///
766+
/// This lint ensures that lifetime parameters are always
767+
/// explicitly stated, even if it is the `'_` [placeholder
768+
/// lifetime].
769+
///
770+
/// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions
771+
pub HIDDEN_LIFETIMES_IN_TYPE_PATHS,
772+
Allow,
773+
"hidden lifetime parameters in types outside function signatures are discouraged"
774+
}
775+
776+
#[derive(Default)]
777+
pub(crate) struct HiddenLifetimesInTypePaths {
778+
inside_fn_signature: bool,
779+
}
780+
781+
impl_lint_pass!(HiddenLifetimesInTypePaths => [HIDDEN_LIFETIMES_IN_TYPE_PATHS]);
782+
783+
impl<'tcx> LateLintPass<'tcx> for HiddenLifetimesInTypePaths {
784+
#[instrument(skip(self, cx))]
785+
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
786+
if self.inside_fn_signature {
787+
return;
788+
}
789+
790+
// Do not lint about usages like `ContainsLifetime::method` or
791+
// `ContainsLifetimeAndType::<SomeType>::method`.
792+
if let hir::Node::Expr(expr) = cx.tcx.parent_hir_node(ty.hir_id)
793+
&& let hir::ExprKind::Path(hir::QPath::TypeRelative(_, seg)) = expr.kind
794+
&& seg.implicit_self()
795+
{
796+
return;
797+
}
798+
799+
let hir::TyKind::Path(path) = ty.kind else { return };
800+
801+
let path_segments = match path {
802+
hir::QPath::Resolved(_ty, path) => path.segments,
803+
804+
hir::QPath::TypeRelative(_ty, path_segment) => slice::from_ref(path_segment),
805+
806+
hir::QPath::LangItem(..) => &[],
807+
};
808+
809+
let mut suggestions = Vec::new();
810+
811+
for path_segment in path_segments {
812+
for arg in path_segment.args().args {
813+
if let hir::GenericArg::Lifetime(lifetime) = arg
814+
&& lifetime.is_implicit()
815+
&& reportable_lifetime_resolution(lifetime.kind)
816+
{
817+
suggestions.push(lifetime.suggestion("'_"))
818+
}
819+
}
820+
}
821+
822+
if suggestions.is_empty() {
823+
return;
824+
}
825+
826+
cx.emit_span_lint(
827+
HIDDEN_LIFETIMES_IN_TYPE_PATHS,
828+
ty.span,
829+
lints::HiddenLifetimeInPath {
830+
suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions },
831+
},
832+
);
833+
}
834+
835+
#[instrument(skip_all)]
836+
fn check_fn(
837+
&mut self,
838+
_: &LateContext<'tcx>,
839+
_: hir::intravisit::FnKind<'tcx>,
840+
_: &'tcx hir::FnDecl<'tcx>,
841+
_: &'tcx hir::Body<'tcx>,
842+
_: rustc_span::Span,
843+
_: rustc_span::def_id::LocalDefId,
844+
) {
845+
// We make the assumption that we will visit the function
846+
// declaration first, before visiting the body.
847+
self.inside_fn_signature = true;
848+
}
849+
850+
// This may be a function's body, which would indicate that we are
851+
// no longer in the signature. Even if it's not, a body cannot
852+
// occur inside a function signature.
853+
#[instrument(skip_all)]
854+
fn check_body(&mut self, _: &LateContext<'tcx>, _: &hir::Body<'tcx>) {
855+
self.inside_fn_signature = false;
856+
}
857+
}

compiler/rustc_lint/src/lints.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3373,3 +3373,24 @@ impl Subdiagnostic for MismatchedLifetimeSyntaxesSuggestion {
33733373
}
33743374
}
33753375
}
3376+
3377+
#[derive(LintDiagnostic)]
3378+
#[diag(lint_hidden_lifetime_in_path)]
3379+
pub(crate) struct HiddenLifetimeInPath {
3380+
#[subdiagnostic]
3381+
pub suggestions: HiddenLifetimeInPathSuggestion,
3382+
}
3383+
3384+
pub(crate) struct HiddenLifetimeInPathSuggestion {
3385+
pub suggestions: Vec<(Span, String)>,
3386+
}
3387+
3388+
impl Subdiagnostic for HiddenLifetimeInPathSuggestion {
3389+
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
3390+
diag.multipart_suggestion_verbose(
3391+
fluent::lint_hidden_lifetime_in_path_suggestion,
3392+
self.suggestions,
3393+
Applicability::MachineApplicable,
3394+
);
3395+
}
3396+
}

0 commit comments

Comments
 (0)