Reword mismatched-lifetime-syntaxes text based on feedback

Key changes include:

- Removal of the word "syntax" from the lint message. More accurately,
  it could have been something like "syntax group" or "syntax
  category", but avoiding it completely is easier.
- The primary lint message now reflects exactly which mismatch is
  occurring, instead of trying to be general. A new `help` line is
  general across the mismatch kinds.
- Suggestions have been reduced to be more minimal, no longer also
  changing non-idiomatic but unrelated aspects.
- Suggestion text no longer mentions changes when those changes don't
  occur in that specific suggestion.
This commit is contained in:
Jake Goulding
2025-07-10 18:36:22 -04:00
parent 9c3064e131
commit 5530744318
26 changed files with 680 additions and 419 deletions

View File

@@ -140,43 +140,115 @@ fn report_mismatches<'tcx>(
}
}
fn lifetimes_use_matched_syntax(input_info: &[Info<'_>], output_info: &[Info<'_>]) -> bool {
// Categorize lifetimes into source/syntax buckets.
let mut n_hidden = 0;
let mut n_elided = 0;
let mut n_named = 0;
#[derive(Debug, Copy, Clone, PartialEq)]
enum LifetimeSyntaxCategory {
Hidden,
Elided,
Named,
}
for info in input_info.iter().chain(output_info) {
impl LifetimeSyntaxCategory {
fn new(syntax_source: (hir::LifetimeSyntax, LifetimeSource)) -> Option<Self> {
use LifetimeSource::*;
use hir::LifetimeSyntax::*;
let syntax_source = (info.lifetime.syntax, info.lifetime.source);
match syntax_source {
// Ignore any other kind of lifetime.
(_, Other) => continue,
// E.g. `&T`.
(Implicit, Reference | OutlivesBound | PreciseCapturing) |
(Implicit, Reference) |
// E.g. `&'_ T`.
(ExplicitAnonymous, Reference | OutlivesBound | PreciseCapturing) |
(ExplicitAnonymous, Reference) |
// E.g. `ContainsLifetime<'_>`.
(ExplicitAnonymous, Path { .. }) => n_elided += 1,
(ExplicitAnonymous, Path { .. }) |
// E.g. `+ '_`, `+ use<'_>`.
(ExplicitAnonymous, OutlivesBound | PreciseCapturing) => {
Some(Self::Elided)
}
// E.g. `ContainsLifetime`.
(Implicit, Path { .. }) => n_hidden += 1,
(Implicit, Path { .. }) => {
Some(Self::Hidden)
}
// E.g. `&'a T`.
(ExplicitBound, Reference | OutlivesBound | PreciseCapturing) |
(ExplicitBound, Reference) |
// E.g. `ContainsLifetime<'a>`.
(ExplicitBound, Path { .. }) => n_named += 1,
};
(ExplicitBound, Path { .. }) |
// E.g. `+ 'a`, `+ use<'a>`.
(ExplicitBound, OutlivesBound | PreciseCapturing) => {
Some(Self::Named)
}
(Implicit, OutlivesBound | PreciseCapturing) |
(_, Other) => {
None
}
}
}
}
#[derive(Debug, Default)]
pub struct LifetimeSyntaxCategories<T> {
pub hidden: T,
pub elided: T,
pub named: T,
}
impl<T> LifetimeSyntaxCategories<T> {
fn select(&mut self, category: LifetimeSyntaxCategory) -> &mut T {
use LifetimeSyntaxCategory::*;
match category {
Elided => &mut self.elided,
Hidden => &mut self.hidden,
Named => &mut self.named,
}
}
}
impl<T> LifetimeSyntaxCategories<Vec<T>> {
pub fn len(&self) -> LifetimeSyntaxCategories<usize> {
LifetimeSyntaxCategories {
hidden: self.hidden.len(),
elided: self.elided.len(),
named: self.named.len(),
}
}
pub fn flatten(&self) -> impl Iterator<Item = &T> {
let Self { hidden, elided, named } = self;
[hidden.iter(), elided.iter(), named.iter()].into_iter().flatten()
}
}
impl std::ops::Add for LifetimeSyntaxCategories<usize> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
hidden: self.hidden + rhs.hidden,
elided: self.elided + rhs.elided,
named: self.named + rhs.named,
}
}
}
fn lifetimes_use_matched_syntax(input_info: &[Info<'_>], output_info: &[Info<'_>]) -> bool {
let mut syntax_counts = LifetimeSyntaxCategories::<usize>::default();
for info in input_info.iter().chain(output_info) {
if let Some(category) = info.lifetime_syntax_category() {
*syntax_counts.select(category) += 1;
}
}
let syntax_counts = (n_hidden, n_elided, n_named);
tracing::debug!(?syntax_counts);
matches!(syntax_counts, (_, 0, 0) | (0, _, 0) | (0, 0, _))
matches!(
syntax_counts,
LifetimeSyntaxCategories { hidden: _, elided: 0, named: 0 }
| LifetimeSyntaxCategories { hidden: 0, elided: _, named: 0 }
| LifetimeSyntaxCategories { hidden: 0, elided: 0, named: _ }
)
}
fn emit_mismatch_diagnostic<'tcx>(
@@ -238,7 +310,7 @@ fn emit_mismatch_diagnostic<'tcx>(
use LifetimeSource::*;
use hir::LifetimeSyntax::*;
let syntax_source = (info.lifetime.syntax, info.lifetime.source);
let syntax_source = info.syntax_source();
if let (_, Other) = syntax_source {
// Ignore any other kind of lifetime.
@@ -259,7 +331,6 @@ fn emit_mismatch_diagnostic<'tcx>(
// E.g. `&'_ T`.
(ExplicitAnonymous, Reference) => {
suggest_change_to_implicit.push(info);
suggest_change_to_mixed_implicit.push(info);
suggest_change_to_explicit_bound.push(info);
}
@@ -319,12 +390,22 @@ fn emit_mismatch_diagnostic<'tcx>(
}
}
let categorize = |infos: &[Info<'_>]| {
let mut categories = LifetimeSyntaxCategories::<Vec<_>>::default();
for info in infos {
if let Some(category) = info.lifetime_syntax_category() {
categories.select(category).push(info.reporting_span());
}
}
categories
};
let inputs = categorize(input_info);
let outputs = categorize(output_info);
let make_implicit_suggestions =
|infos: &[&Info<'_>]| infos.iter().map(|i| i.removing_span()).collect::<Vec<_>>();
let inputs = input_info.iter().map(|info| info.reporting_span()).collect();
let outputs = output_info.iter().map(|info| info.reporting_span()).collect();
let explicit_bound_suggestion = bound_lifetime.map(|info| {
build_mismatch_suggestion(info.lifetime_name(), &suggest_change_to_explicit_bound)
});
@@ -399,8 +480,6 @@ fn emit_mismatch_diagnostic<'tcx>(
?explicit_anonymous_suggestion,
);
let lifetime_name = bound_lifetime.map(|info| info.lifetime_name()).unwrap_or("'_").to_owned();
// We can produce a number of suggestions which may overwhelm
// the user. Instead, we order the suggestions based on Rust
// idioms. The "best" choice is shown to the user and the
@@ -413,8 +492,8 @@ fn emit_mismatch_diagnostic<'tcx>(
cx.emit_span_lint(
MISMATCHED_LIFETIME_SYNTAXES,
Vec::clone(&inputs),
lints::MismatchedLifetimeSyntaxes { lifetime_name, inputs, outputs, suggestions },
inputs.flatten().copied().collect::<Vec<_>>(),
lints::MismatchedLifetimeSyntaxes { inputs, outputs, suggestions },
);
}
@@ -422,12 +501,12 @@ fn build_mismatch_suggestion(
lifetime_name: &str,
infos: &[&Info<'_>],
) -> lints::MismatchedLifetimeSyntaxesSuggestion {
let lifetime_name_sugg = lifetime_name.to_owned();
let lifetime_name = lifetime_name.to_owned();
let suggestions = infos.iter().map(|info| info.suggestion(&lifetime_name)).collect();
lints::MismatchedLifetimeSyntaxesSuggestion::Explicit {
lifetime_name_sugg,
lifetime_name,
suggestions,
tool_only: false,
}
@@ -441,6 +520,14 @@ struct Info<'tcx> {
}
impl<'tcx> Info<'tcx> {
fn syntax_source(&self) -> (hir::LifetimeSyntax, LifetimeSource) {
(self.lifetime.syntax, self.lifetime.source)
}
fn lifetime_syntax_category(&self) -> Option<LifetimeSyntaxCategory> {
LifetimeSyntaxCategory::new(self.syntax_source())
}
fn lifetime_name(&self) -> &str {
self.lifetime.ident.as_str()
}