Skip to content

Commit db43546

Browse files
committed
Auto merge of #146913 - camsteffen:refactor-lint-syntax, r=fee1-dead
mismatched_lifetime_syntax lint refactors and optimizations I found several opportunities to return early so I'm hoping those will have a perf improvement. Otherwise, it's various refactors for simplicity.
2 parents 3369e82 + c4074bd commit db43546

File tree

2 files changed

+82
-130
lines changed

2 files changed

+82
-130
lines changed

compiler/rustc_lint/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
2222
// tidy-alphabetical-start
2323
#![allow(internal_features)]
24+
#![cfg_attr(bootstrap, feature(iter_chain))]
2425
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
2526
#![doc(rust_logo)]
2627
#![feature(array_windows)]

compiler/rustc_lint/src/lifetime_syntax.rs

Lines changed: 81 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use rustc_hir::intravisit::{self, Visitor};
33
use rustc_hir::{self as hir, LifetimeSource};
44
use rustc_session::{declare_lint, declare_lint_pass};
55
use rustc_span::Span;
6+
use rustc_span::def_id::LocalDefId;
67
use tracing::instrument;
78

89
use crate::{LateContext, LateLintPass, LintContext, lints};
@@ -78,11 +79,11 @@ impl<'tcx> LateLintPass<'tcx> for LifetimeSyntax {
7879
fn check_fn(
7980
&mut self,
8081
cx: &LateContext<'tcx>,
81-
_: hir::intravisit::FnKind<'tcx>,
82+
_: intravisit::FnKind<'tcx>,
8283
fd: &'tcx hir::FnDecl<'tcx>,
8384
_: &'tcx hir::Body<'tcx>,
84-
_: rustc_span::Span,
85-
_: rustc_span::def_id::LocalDefId,
85+
_: Span,
86+
_: LocalDefId,
8687
) {
8788
check_fn_like(cx, fd);
8889
}
@@ -97,11 +98,7 @@ impl<'tcx> LateLintPass<'tcx> for LifetimeSyntax {
9798
}
9899

99100
#[instrument(skip_all)]
100-
fn check_foreign_item(
101-
&mut self,
102-
cx: &LateContext<'tcx>,
103-
fi: &'tcx rustc_hir::ForeignItem<'tcx>,
104-
) {
101+
fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, fi: &'tcx hir::ForeignItem<'tcx>) {
105102
match fi.kind {
106103
hir::ForeignItemKind::Fn(fn_sig, _idents, _generics) => check_fn_like(cx, fn_sig.decl),
107104
hir::ForeignItemKind::Static(..) => {}
@@ -111,35 +108,47 @@ impl<'tcx> LateLintPass<'tcx> for LifetimeSyntax {
111108
}
112109

113110
fn check_fn_like<'tcx>(cx: &LateContext<'tcx>, fd: &'tcx hir::FnDecl<'tcx>) {
114-
let mut input_map = Default::default();
115-
let mut output_map = Default::default();
116-
117-
for input in fd.inputs {
118-
LifetimeInfoCollector::collect(input, &mut input_map);
111+
if fd.inputs.is_empty() {
112+
return;
119113
}
114+
let hir::FnRetTy::Return(output) = fd.output else {
115+
return;
116+
};
120117

121-
if let hir::FnRetTy::Return(output) = fd.output {
122-
LifetimeInfoCollector::collect(output, &mut output_map);
123-
}
118+
let mut map: FxIndexMap<hir::LifetimeKind, LifetimeGroup<'_>> = FxIndexMap::default();
124119

125-
report_mismatches(cx, &input_map, &output_map);
126-
}
120+
LifetimeInfoCollector::collect(output, |info| {
121+
let group = map.entry(info.lifetime.kind).or_default();
122+
group.outputs.push(info);
123+
});
124+
if map.is_empty() {
125+
return;
126+
}
127127

128-
#[instrument(skip_all)]
129-
fn report_mismatches<'tcx>(
130-
cx: &LateContext<'tcx>,
131-
inputs: &LifetimeInfoMap<'tcx>,
132-
outputs: &LifetimeInfoMap<'tcx>,
133-
) {
134-
for (resolved_lifetime, output_info) in outputs {
135-
if let Some(input_info) = inputs.get(resolved_lifetime) {
136-
if !lifetimes_use_matched_syntax(input_info, output_info) {
137-
emit_mismatch_diagnostic(cx, input_info, output_info);
128+
for input in fd.inputs {
129+
LifetimeInfoCollector::collect(input, |info| {
130+
if let Some(group) = map.get_mut(&info.lifetime.kind) {
131+
group.inputs.push(info);
138132
}
133+
});
134+
}
135+
136+
for LifetimeGroup { ref inputs, ref outputs } in map.into_values() {
137+
if inputs.is_empty() {
138+
continue;
139+
}
140+
if !lifetimes_use_matched_syntax(inputs, outputs) {
141+
emit_mismatch_diagnostic(cx, inputs, outputs);
139142
}
140143
}
141144
}
142145

146+
#[derive(Default)]
147+
struct LifetimeGroup<'tcx> {
148+
inputs: Vec<Info<'tcx>>,
149+
outputs: Vec<Info<'tcx>>,
150+
}
151+
143152
#[derive(Debug, Copy, Clone, PartialEq)]
144153
enum LifetimeSyntaxCategory {
145154
Hidden,
@@ -148,11 +157,11 @@ enum LifetimeSyntaxCategory {
148157
}
149158

150159
impl LifetimeSyntaxCategory {
151-
fn new(syntax_source: (hir::LifetimeSyntax, LifetimeSource)) -> Option<Self> {
160+
fn new(lifetime: &hir::Lifetime) -> Option<Self> {
152161
use LifetimeSource::*;
153162
use hir::LifetimeSyntax::*;
154163

155-
match syntax_source {
164+
match (lifetime.syntax, lifetime.source) {
156165
// E.g. `&T`.
157166
(Implicit, Reference) |
158167
// E.g. `&'_ T`.
@@ -216,7 +225,7 @@ impl<T> LifetimeSyntaxCategories<Vec<T>> {
216225

217226
pub fn iter_unnamed(&self) -> impl Iterator<Item = &T> {
218227
let Self { hidden, elided, named: _ } = self;
219-
[hidden.iter(), elided.iter()].into_iter().flatten()
228+
std::iter::chain(hidden, elided)
220229
}
221230
}
222231

@@ -233,22 +242,8 @@ impl std::ops::Add for LifetimeSyntaxCategories<usize> {
233242
}
234243

235244
fn lifetimes_use_matched_syntax(input_info: &[Info<'_>], output_info: &[Info<'_>]) -> bool {
236-
let mut syntax_counts = LifetimeSyntaxCategories::<usize>::default();
237-
238-
for info in input_info.iter().chain(output_info) {
239-
if let Some(category) = info.lifetime_syntax_category() {
240-
*syntax_counts.select(category) += 1;
241-
}
242-
}
243-
244-
tracing::debug!(?syntax_counts);
245-
246-
matches!(
247-
syntax_counts,
248-
LifetimeSyntaxCategories { hidden: _, elided: 0, named: 0 }
249-
| LifetimeSyntaxCategories { hidden: 0, elided: _, named: 0 }
250-
| LifetimeSyntaxCategories { hidden: 0, elided: 0, named: _ }
251-
)
245+
let (first, inputs) = input_info.split_first().unwrap();
246+
std::iter::chain(inputs, output_info).all(|info| info.syntax_category == first.syntax_category)
252247
}
253248

254249
fn emit_mismatch_diagnostic<'tcx>(
@@ -310,18 +305,13 @@ fn emit_mismatch_diagnostic<'tcx>(
310305
use LifetimeSource::*;
311306
use hir::LifetimeSyntax::*;
312307

313-
let syntax_source = info.syntax_source();
308+
let lifetime = info.lifetime;
314309

315-
if let (_, Other) = syntax_source {
316-
// Ignore any other kind of lifetime.
317-
continue;
318-
}
319-
320-
if let (ExplicitBound, _) = syntax_source {
310+
if lifetime.syntax == ExplicitBound {
321311
bound_lifetime = Some(info);
322312
}
323313

324-
match syntax_source {
314+
match (lifetime.syntax, lifetime.source) {
325315
// E.g. `&T`.
326316
(Implicit, Reference) => {
327317
suggest_change_to_explicit_anonymous.push(info);
@@ -341,8 +331,8 @@ fn emit_mismatch_diagnostic<'tcx>(
341331
suggest_change_to_explicit_bound.push(info);
342332
}
343333

344-
// E.g. `ContainsLifetime<'_>`.
345-
(ExplicitAnonymous, Path { .. }) => {
334+
// E.g. `ContainsLifetime<'_>`, `+ '_`, `+ use<'_>`.
335+
(ExplicitAnonymous, Path { .. } | OutlivesBound | PreciseCapturing) => {
346336
suggest_change_to_explicit_bound.push(info);
347337
}
348338

@@ -353,8 +343,8 @@ fn emit_mismatch_diagnostic<'tcx>(
353343
suggest_change_to_explicit_anonymous.push(info);
354344
}
355345

356-
// E.g. `ContainsLifetime<'a>`.
357-
(ExplicitBound, Path { .. }) => {
346+
// E.g. `ContainsLifetime<'a>`, `+ 'a`, `+ use<'a>`.
347+
(ExplicitBound, Path { .. } | OutlivesBound | PreciseCapturing) => {
358348
suggest_change_to_mixed_explicit_anonymous.push(info);
359349
suggest_change_to_explicit_anonymous.push(info);
360350
}
@@ -363,39 +353,26 @@ fn emit_mismatch_diagnostic<'tcx>(
363353
panic!("This syntax / source combination is not possible");
364354
}
365355

366-
// E.g. `+ '_`, `+ use<'_>`.
367-
(ExplicitAnonymous, OutlivesBound | PreciseCapturing) => {
368-
suggest_change_to_explicit_bound.push(info);
369-
}
370-
371-
// E.g. `+ 'a`, `+ use<'a>`.
372-
(ExplicitBound, OutlivesBound | PreciseCapturing) => {
373-
suggest_change_to_mixed_explicit_anonymous.push(info);
374-
suggest_change_to_explicit_anonymous.push(info);
375-
}
376-
377356
(_, Other) => {
378357
panic!("This syntax / source combination has already been skipped");
379358
}
380359
}
381360

382-
if matches!(syntax_source, (_, Path { .. } | OutlivesBound | PreciseCapturing)) {
361+
if matches!(lifetime.source, Path { .. } | OutlivesBound | PreciseCapturing) {
383362
allow_suggesting_implicit = false;
384363
}
385364

386-
match syntax_source {
387-
(_, Reference) => saw_a_reference = true,
388-
(_, Path { .. }) => saw_a_path = true,
365+
match lifetime.source {
366+
Reference => saw_a_reference = true,
367+
Path { .. } => saw_a_path = true,
389368
_ => {}
390369
}
391370
}
392371

393372
let categorize = |infos: &[Info<'_>]| {
394373
let mut categories = LifetimeSyntaxCategories::<Vec<_>>::default();
395374
for info in infos {
396-
if let Some(category) = info.lifetime_syntax_category() {
397-
categories.select(category).push(info.reporting_span());
398-
}
375+
categories.select(info.syntax_category).push(info.reporting_span());
399376
}
400377
categories
401378
};
@@ -407,10 +384,10 @@ fn emit_mismatch_diagnostic<'tcx>(
407384
|infos: &[&Info<'_>]| infos.iter().map(|i| i.removing_span()).collect::<Vec<_>>();
408385

409386
let explicit_bound_suggestion = bound_lifetime.map(|info| {
410-
build_mismatch_suggestion(info.lifetime_name(), &suggest_change_to_explicit_bound)
387+
build_mismatch_suggestion(info.lifetime.ident.as_str(), &suggest_change_to_explicit_bound)
411388
});
412389

413-
let is_bound_static = bound_lifetime.is_some_and(|info| info.is_static());
390+
let is_bound_static = bound_lifetime.is_some_and(|info| info.lifetime.is_static());
414391

415392
tracing::debug!(?bound_lifetime, ?explicit_bound_suggestion, ?is_bound_static);
416393

@@ -517,33 +494,17 @@ fn build_mismatch_suggestion(
517494

518495
#[derive(Debug)]
519496
struct Info<'tcx> {
520-
type_span: Span,
521-
referenced_type_span: Option<Span>,
522497
lifetime: &'tcx hir::Lifetime,
498+
syntax_category: LifetimeSyntaxCategory,
499+
ty: &'tcx hir::Ty<'tcx>,
523500
}
524501

525502
impl<'tcx> Info<'tcx> {
526-
fn syntax_source(&self) -> (hir::LifetimeSyntax, LifetimeSource) {
527-
(self.lifetime.syntax, self.lifetime.source)
528-
}
529-
530-
fn lifetime_syntax_category(&self) -> Option<LifetimeSyntaxCategory> {
531-
LifetimeSyntaxCategory::new(self.syntax_source())
532-
}
533-
534-
fn lifetime_name(&self) -> &str {
535-
self.lifetime.ident.as_str()
536-
}
537-
538-
fn is_static(&self) -> bool {
539-
self.lifetime.is_static()
540-
}
541-
542503
/// When reporting a lifetime that is implicit, we expand the span
543504
/// to include the type. Otherwise we end up pointing at nothing,
544505
/// which is a bit confusing.
545506
fn reporting_span(&self) -> Span {
546-
if self.lifetime.is_implicit() { self.type_span } else { self.lifetime.ident.span }
507+
if self.lifetime.is_implicit() { self.ty.span } else { self.lifetime.ident.span }
547508
}
548509

549510
/// When removing an explicit lifetime from a reference,
@@ -560,12 +521,10 @@ impl<'tcx> Info<'tcx> {
560521
/// ```
561522
// FIXME: Ideally, we'd also remove the lifetime declaration.
562523
fn removing_span(&self) -> Span {
563-
let mut span = self.suggestion("'dummy").0;
564-
565-
if let Some(referenced_type_span) = self.referenced_type_span {
566-
span = span.until(referenced_type_span);
524+
let mut span = self.lifetime.ident.span;
525+
if let hir::TyKind::Ref(_, mut_ty) = self.ty.kind {
526+
span = span.until(mut_ty.ty.span);
567527
}
568-
569528
span
570529
}
571530

@@ -574,46 +533,38 @@ impl<'tcx> Info<'tcx> {
574533
}
575534
}
576535

577-
type LifetimeInfoMap<'tcx> = FxIndexMap<&'tcx hir::LifetimeKind, Vec<Info<'tcx>>>;
578-
579-
struct LifetimeInfoCollector<'a, 'tcx> {
580-
type_span: Span,
581-
referenced_type_span: Option<Span>,
582-
map: &'a mut LifetimeInfoMap<'tcx>,
536+
struct LifetimeInfoCollector<'tcx, F> {
537+
info_func: F,
538+
ty: &'tcx hir::Ty<'tcx>,
583539
}
584540

585-
impl<'a, 'tcx> LifetimeInfoCollector<'a, 'tcx> {
586-
fn collect(ty: &'tcx hir::Ty<'tcx>, map: &'a mut LifetimeInfoMap<'tcx>) {
587-
let mut this = Self { type_span: ty.span, referenced_type_span: None, map };
541+
impl<'tcx, F> LifetimeInfoCollector<'tcx, F>
542+
where
543+
F: FnMut(Info<'tcx>),
544+
{
545+
fn collect(ty: &'tcx hir::Ty<'tcx>, info_func: F) {
546+
let mut this = Self { info_func, ty };
588547

589548
intravisit::walk_unambig_ty(&mut this, ty);
590549
}
591550
}
592551

593-
impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> {
552+
impl<'tcx, F> Visitor<'tcx> for LifetimeInfoCollector<'tcx, F>
553+
where
554+
F: FnMut(Info<'tcx>),
555+
{
594556
#[instrument(skip(self))]
595557
fn visit_lifetime(&mut self, lifetime: &'tcx hir::Lifetime) {
596-
let type_span = self.type_span;
597-
let referenced_type_span = self.referenced_type_span;
598-
599-
let info = Info { type_span, referenced_type_span, lifetime };
600-
601-
self.map.entry(&lifetime.kind).or_default().push(info);
558+
if let Some(syntax_category) = LifetimeSyntaxCategory::new(lifetime) {
559+
let info = Info { lifetime, syntax_category, ty: self.ty };
560+
(self.info_func)(info);
561+
}
602562
}
603563

604564
#[instrument(skip(self))]
605565
fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) -> Self::Result {
606-
let old_type_span = self.type_span;
607-
let old_referenced_type_span = self.referenced_type_span;
608-
609-
self.type_span = ty.span;
610-
if let hir::TyKind::Ref(_, ty) = ty.kind {
611-
self.referenced_type_span = Some(ty.ty.span);
612-
}
613-
566+
let old_ty = std::mem::replace(&mut self.ty, ty.as_unambig_ty());
614567
intravisit::walk_ty(self, ty);
615-
616-
self.type_span = old_type_span;
617-
self.referenced_type_span = old_referenced_type_span;
568+
self.ty = old_ty;
618569
}
619570
}

0 commit comments

Comments
 (0)