Skip to content

Commit 4e645f4

Browse files
committed
Add new HIR implementations of hidden lifetimes in paths lints
1 parent 6a76e20 commit 4e645f4

File tree

4 files changed

+302
-2
lines changed

4 files changed

+302
-2
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ lint_hidden_glob_reexport = private item shadows public glob re-export
292292
.note_glob_reexport = the name `{$name}` in the {$namespace} namespace is supposed to be publicly re-exported here
293293
.note_private_item = but the private item here shadows it
294294
295+
lint_hidden_lifetime_in_path =
296+
paths containing hidden lifetime parameters are deprecated
297+
298+
lint_hidden_lifetime_in_path_suggestion =
299+
indicate the anonymous lifetime
300+
295301
lint_hidden_lifetime_parameters = hidden lifetime parameters in types are deprecated
296302
297303
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: 274 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,149 @@ 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+
// Do not lint about usages such as `ContainsLifetime::method` or
784+
// `ContainsLifetimeAndType::<SomeType>::method`.
785+
fn parent_path_implicit_self<'tcx>(cx: &LateContext<'tcx>, parent_hir_id: hir::HirId) -> bool {
786+
let parent_hir_node = cx.tcx.parent_hir_node(parent_hir_id);
787+
tracing::debug!(?parent_hir_node, "parent_hir_node");
788+
789+
fn path_is_implicit_self(path: hir::QPath<'_>) -> bool {
790+
match path {
791+
hir::QPath::TypeRelative(_, seg) => seg.implicit_self(),
792+
_ => false,
793+
}
794+
}
795+
796+
if let hir::Node::Expr(expr) = parent_hir_node {
797+
return match expr.kind {
798+
hir::ExprKind::Path(path) => path_is_implicit_self(path),
799+
hir::ExprKind::Struct(path, _, _) => path_is_implicit_self(*path),
800+
_ => false,
801+
};
802+
}
803+
804+
if let hir::Node::Pat(pat) = parent_hir_node
805+
&& let hir::PatKind::Struct(path, _, _) | hir::PatKind::TupleStruct(path, _, _) = pat.kind
806+
{
807+
return path_is_implicit_self(path);
808+
}
809+
810+
if let hir::Node::PatExpr(expr) = parent_hir_node
811+
&& let hir::PatExprKind::Path(path) = expr.kind
812+
{
813+
return path_is_implicit_self(path);
814+
}
815+
816+
false
817+
}
818+
819+
impl<'tcx> LateLintPass<'tcx> for HiddenLifetimesInTypePaths {
820+
#[instrument(skip(self, cx))]
821+
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
822+
if self.inside_fn_signature {
823+
return;
824+
}
825+
826+
if parent_path_implicit_self(cx, ty.hir_id) {
827+
return;
828+
}
829+
830+
let hir::TyKind::Path(path) = ty.kind else { return };
831+
832+
let path_segments = match path {
833+
hir::QPath::Resolved(_ty, path) => path.segments,
834+
835+
hir::QPath::TypeRelative(_ty, path_segment) => slice::from_ref(path_segment),
836+
837+
hir::QPath::LangItem(..) => &[],
838+
};
839+
840+
let mut suggestions = Vec::new();
841+
842+
for path_segment in path_segments {
843+
for arg in path_segment.args().args {
844+
if let hir::GenericArg::Lifetime(lifetime) = arg
845+
&& lifetime.is_implicit()
846+
&& reportable_lifetime_resolution(lifetime.kind)
847+
{
848+
suggestions.push(lifetime.suggestion("'_"))
849+
}
850+
}
851+
}
852+
853+
if suggestions.is_empty() {
854+
return;
855+
}
856+
857+
cx.emit_span_lint(
858+
HIDDEN_LIFETIMES_IN_TYPE_PATHS,
859+
ty.span,
860+
lints::HiddenLifetimeInPath {
861+
suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions },
862+
},
863+
);
864+
}
865+
866+
#[instrument(skip_all)]
867+
fn check_fn(
868+
&mut self,
869+
_: &LateContext<'tcx>,
870+
_: hir::intravisit::FnKind<'tcx>,
871+
_: &'tcx hir::FnDecl<'tcx>,
872+
_: &'tcx hir::Body<'tcx>,
873+
_: rustc_span::Span,
874+
_: rustc_span::def_id::LocalDefId,
875+
) {
876+
// We make the assumption that we will visit the function
877+
// declaration first, before visiting the body.
878+
self.inside_fn_signature = true;
879+
}
880+
881+
// This may be a function's body, which would indicate that we are
882+
// no longer in the signature. Even if it's not, a body cannot
883+
// occur inside a function signature.
884+
#[instrument(skip_all)]
885+
fn check_body(&mut self, _: &LateContext<'tcx>, _: &hir::Body<'tcx>) {
886+
self.inside_fn_signature = false;
887+
}
888+
}

compiler/rustc_lint/src/lints.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3389,3 +3389,24 @@ impl Subdiagnostic for MismatchedLifetimeSyntaxesSuggestion {
33893389
}
33903390
}
33913391
}
3392+
3393+
#[derive(LintDiagnostic)]
3394+
#[diag(lint_hidden_lifetime_in_path)]
3395+
pub(crate) struct HiddenLifetimeInPath {
3396+
#[subdiagnostic]
3397+
pub suggestions: HiddenLifetimeInPathSuggestion,
3398+
}
3399+
3400+
pub(crate) struct HiddenLifetimeInPathSuggestion {
3401+
pub suggestions: Vec<(Span, String)>,
3402+
}
3403+
3404+
impl Subdiagnostic for HiddenLifetimeInPathSuggestion {
3405+
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
3406+
diag.multipart_suggestion_verbose(
3407+
fluent::lint_hidden_lifetime_in_path_suggestion,
3408+
self.suggestions,
3409+
Applicability::MachineApplicable,
3410+
);
3411+
}
3412+
}

0 commit comments

Comments
 (0)