diff options
Diffstat (limited to 'compiler/rustc_lint/src')
| -rw-r--r-- | compiler/rustc_lint/src/lib.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lifetime_syntax.rs | 145 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lints.rs | 64 |
3 files changed, 167 insertions, 44 deletions
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 20568f35a47..27ce614d8c9 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -55,7 +55,7 @@ mod invalid_from_utf8; mod late; mod let_underscore; mod levels; -mod lifetime_syntax; +pub mod lifetime_syntax; mod lints; mod macro_expr_fragment_specifier_2024_migration; mod map_unit_fn; diff --git a/compiler/rustc_lint/src/lifetime_syntax.rs b/compiler/rustc_lint/src/lifetime_syntax.rs index 95b7b69bd5a..2a5a34cdc6e 100644 --- a/compiler/rustc_lint/src/lifetime_syntax.rs +++ b/compiler/rustc_lint/src/lifetime_syntax.rs @@ -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 }, ); } @@ -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() } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index c4a6695efe9..1728f7d2902 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -22,6 +22,7 @@ use rustc_span::{Ident, MacroRulesNormalizedIdent, Span, Symbol, sym}; use crate::builtin::{InitError, ShorthandAssocTyCollector, TypeAliasBounds}; use crate::errors::{OverruledAttributeSub, RequestedLevel}; +use crate::lifetime_syntax::LifetimeSyntaxCategories; use crate::{LateContext, fluent_generated as fluent}; // array_into_iter.rs @@ -3176,30 +3177,59 @@ pub(crate) struct ReservedMultihash { #[derive(Debug)] pub(crate) struct MismatchedLifetimeSyntaxes { - pub lifetime_name: String, - pub inputs: Vec<Span>, - pub outputs: Vec<Span>, + pub inputs: LifetimeSyntaxCategories<Vec<Span>>, + pub outputs: LifetimeSyntaxCategories<Vec<Span>>, pub suggestions: Vec<MismatchedLifetimeSyntaxesSuggestion>, } impl<'a, G: EmissionGuarantee> LintDiagnostic<'a, G> for MismatchedLifetimeSyntaxes { fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>) { - diag.primary_message(fluent::lint_mismatched_lifetime_syntaxes); + let counts = self.inputs.len() + self.outputs.len(); + let message = match counts { + LifetimeSyntaxCategories { hidden: 0, elided: 0, named: 0 } => { + panic!("No lifetime mismatch detected") + } + + LifetimeSyntaxCategories { hidden: _, elided: _, named: 0 } => { + fluent::lint_mismatched_lifetime_syntaxes_hiding_while_elided + } + + LifetimeSyntaxCategories { hidden: _, elided: 0, named: _ } => { + fluent::lint_mismatched_lifetime_syntaxes_hiding_while_named + } + + LifetimeSyntaxCategories { hidden: 0, elided: _, named: _ } => { + fluent::lint_mismatched_lifetime_syntaxes_eliding_while_named + } - diag.arg("lifetime_name", self.lifetime_name); + LifetimeSyntaxCategories { hidden: _, elided: _, named: _ } => { + fluent::lint_mismatched_lifetime_syntaxes_hiding_and_eliding_while_named + } + }; + diag.primary_message(message); - diag.arg("n_inputs", self.inputs.len()); - for input in self.inputs { - let a = diag.eagerly_translate(fluent::lint_label_mismatched_lifetime_syntaxes_inputs); - diag.span_label(input, a); + for s in self.inputs.hidden { + diag.span_label(s, fluent::lint_mismatched_lifetime_syntaxes_input_hidden); + } + for s in self.inputs.elided { + diag.span_label(s, fluent::lint_mismatched_lifetime_syntaxes_input_elided); + } + for s in self.inputs.named { + diag.span_label(s, fluent::lint_mismatched_lifetime_syntaxes_input_named); } - diag.arg("n_outputs", self.outputs.len()); - for output in self.outputs { - let a = diag.eagerly_translate(fluent::lint_label_mismatched_lifetime_syntaxes_outputs); - diag.span_label(output, a); + for s in self.outputs.hidden { + diag.span_label(s, fluent::lint_mismatched_lifetime_syntaxes_output_hidden); + } + for s in self.outputs.elided { + diag.span_label(s, fluent::lint_mismatched_lifetime_syntaxes_output_elided); } + for s in self.outputs.named { + diag.span_label(s, fluent::lint_mismatched_lifetime_syntaxes_output_named); + } + + diag.help(fluent::lint_mismatched_lifetime_syntaxes_help); let mut suggestions = self.suggestions.into_iter(); if let Some(s) = suggestions.next() { @@ -3267,6 +3297,12 @@ impl Subdiagnostic for MismatchedLifetimeSyntaxesSuggestion { } Mixed { implicit_suggestions, explicit_anonymous_suggestions, tool_only } => { + let message = if implicit_suggestions.is_empty() { + fluent::lint_mismatched_lifetime_syntaxes_suggestion_mixed_only_paths + } else { + fluent::lint_mismatched_lifetime_syntaxes_suggestion_mixed + }; + let implicit_suggestions = implicit_suggestions.into_iter().map(|s| (s, String::new())); @@ -3274,7 +3310,7 @@ impl Subdiagnostic for MismatchedLifetimeSyntaxesSuggestion { implicit_suggestions.chain(explicit_anonymous_suggestions).collect(); diag.multipart_suggestion_with_style( - fluent::lint_mismatched_lifetime_syntaxes_suggestion_mixed, + message, suggestions, Applicability::MaybeIncorrect, style(tool_only), |
