Rollup merge of #136577 - dianne:simple-pat-migration-simplification, r=Nadrieril
Pattern Migration 2024: try to suggest eliding redundant binding modifiers This is based on #136475. Only the last commit is new. This is a simpler, more restrictive alternative to #136496, meant to partially address #136047. If a pattern can be migrated to Rust 2024 solely by removing redundant binding modifiers, this will make that suggestion; otherwise, it uses the old suggestion of making the pattern fully explicit. Relevant tracking issue: #131414 ``@rustbot`` label A-diagnostics A-patterns A-edition-2024 r? ``@Nadrieril``
This commit is contained in:
commit
20f9e973d9
15 changed files with 1043 additions and 262 deletions
|
@ -285,7 +285,16 @@ mir_build_pointer_pattern = function pointers and raw pointers not derived from
|
|||
|
||||
mir_build_privately_uninhabited = pattern `{$witness_1}` is currently uninhabited, but this variant contains private fields which may become inhabited in the future
|
||||
|
||||
mir_build_rust_2024_incompatible_pat = this pattern relies on behavior which may change in edition 2024
|
||||
mir_build_rust_2024_incompatible_pat = {$bad_modifiers ->
|
||||
*[true] binding modifiers{$bad_ref_pats ->
|
||||
*[true] {" "}and reference patterns
|
||||
[false] {""}
|
||||
}
|
||||
[false] reference patterns
|
||||
} may only be written when the default binding mode is `move`{$is_hard_error ->
|
||||
*[true] {""}
|
||||
[false] {" "}in Rust 2024
|
||||
}
|
||||
|
||||
mir_build_static_in_pattern = statics cannot be referenced in patterns
|
||||
.label = can't be used in patterns
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_errors::codes::*;
|
||||
use rustc_errors::{
|
||||
Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level,
|
||||
|
@ -1097,41 +1098,70 @@ pub(crate) enum MiscPatternSuggestion {
|
|||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(mir_build_rust_2024_incompatible_pat)]
|
||||
pub(crate) struct Rust2024IncompatiblePat<'a> {
|
||||
pub(crate) struct Rust2024IncompatiblePat {
|
||||
#[subdiagnostic]
|
||||
pub(crate) sugg: Rust2024IncompatiblePatSugg<'a>,
|
||||
pub(crate) sugg: Rust2024IncompatiblePatSugg,
|
||||
pub(crate) bad_modifiers: bool,
|
||||
pub(crate) bad_ref_pats: bool,
|
||||
pub(crate) is_hard_error: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct Rust2024IncompatiblePatSugg<'a> {
|
||||
pub(crate) struct Rust2024IncompatiblePatSugg {
|
||||
/// If true, our suggestion is to elide explicit binding modifiers.
|
||||
/// If false, our suggestion is to make the pattern fully explicit.
|
||||
pub(crate) suggest_eliding_modes: bool,
|
||||
pub(crate) suggestion: Vec<(Span, String)>,
|
||||
pub(crate) ref_pattern_count: usize,
|
||||
pub(crate) binding_mode_count: usize,
|
||||
/// Labeled spans for subpatterns invalid in Rust 2024.
|
||||
pub(crate) labels: &'a [(Span, String)],
|
||||
/// Internal state: the ref-mutability of the default binding mode at the subpattern being
|
||||
/// lowered, with the span where it was introduced. `None` for a by-value default mode.
|
||||
pub(crate) default_mode_span: Option<(Span, ty::Mutability)>,
|
||||
/// Labels for where incompatibility-causing by-ref default binding modes were introduced.
|
||||
pub(crate) default_mode_labels: FxIndexMap<Span, ty::Mutability>,
|
||||
}
|
||||
|
||||
impl<'a> Subdiagnostic for Rust2024IncompatiblePatSugg<'a> {
|
||||
impl Subdiagnostic for Rust2024IncompatiblePatSugg {
|
||||
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
|
||||
self,
|
||||
diag: &mut Diag<'_, G>,
|
||||
_f: &F,
|
||||
) {
|
||||
// Format and emit explanatory notes about default binding modes. Reversing the spans' order
|
||||
// means if we have nested spans, the innermost ones will be visited first.
|
||||
for (span, def_br_mutbl) in self.default_mode_labels.into_iter().rev() {
|
||||
// Don't point to a macro call site.
|
||||
if !span.from_expansion() {
|
||||
let note_msg = "matching on a reference type with a non-reference pattern changes the default binding mode";
|
||||
let label_msg =
|
||||
format!("this matches on type `{}_`", def_br_mutbl.ref_prefix_str());
|
||||
let mut label = MultiSpan::from(span);
|
||||
label.push_span_label(span, label_msg);
|
||||
diag.span_note(label, note_msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Format and emit the suggestion.
|
||||
let applicability =
|
||||
if self.suggestion.iter().all(|(span, _)| span.can_be_used_for_suggestions()) {
|
||||
Applicability::MachineApplicable
|
||||
} else {
|
||||
Applicability::MaybeIncorrect
|
||||
};
|
||||
let plural_derefs = pluralize!(self.ref_pattern_count);
|
||||
let and_modes = if self.binding_mode_count > 0 {
|
||||
format!(" and variable binding mode{}", pluralize!(self.binding_mode_count))
|
||||
let msg = if self.suggest_eliding_modes {
|
||||
let plural_modes = pluralize!(self.binding_mode_count);
|
||||
format!("remove the unnecessary binding modifier{plural_modes}")
|
||||
} else {
|
||||
String::new()
|
||||
let plural_derefs = pluralize!(self.ref_pattern_count);
|
||||
let and_modes = if self.binding_mode_count > 0 {
|
||||
format!(" and variable binding mode{}", pluralize!(self.binding_mode_count))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
format!("make the implied reference pattern{plural_derefs}{and_modes} explicit")
|
||||
};
|
||||
diag.multipart_suggestion_verbose(
|
||||
format!("make the implied reference pattern{plural_derefs}{and_modes} explicit"),
|
||||
self.suggestion,
|
||||
applicability,
|
||||
);
|
||||
// FIXME(dianne): for peace of mind, don't risk emitting a 0-part suggestion (that panics!)
|
||||
if !self.suggestion.is_empty() {
|
||||
diag.multipart_suggestion_verbose(msg, self.suggestion, applicability);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ struct PatCtxt<'a, 'tcx> {
|
|||
typeck_results: &'a ty::TypeckResults<'tcx>,
|
||||
|
||||
/// Used by the Rust 2024 migration lint.
|
||||
rust_2024_migration_suggestion: Option<Rust2024IncompatiblePatSugg<'a>>,
|
||||
rust_2024_migration_suggestion: Option<Rust2024IncompatiblePatSugg>,
|
||||
}
|
||||
|
||||
pub(super) fn pat_from_hir<'a, 'tcx>(
|
||||
|
@ -44,25 +44,30 @@ pub(super) fn pat_from_hir<'a, 'tcx>(
|
|||
typeck_results: &'a ty::TypeckResults<'tcx>,
|
||||
pat: &'tcx hir::Pat<'tcx>,
|
||||
) -> Box<Pat<'tcx>> {
|
||||
let migration_info = typeck_results.rust_2024_migration_desugared_pats().get(pat.hir_id);
|
||||
let mut pcx = PatCtxt {
|
||||
tcx,
|
||||
typing_env,
|
||||
typeck_results,
|
||||
rust_2024_migration_suggestion: typeck_results
|
||||
.rust_2024_migration_desugared_pats()
|
||||
.get(pat.hir_id)
|
||||
.map(|labels| Rust2024IncompatiblePatSugg {
|
||||
rust_2024_migration_suggestion: migration_info.and_then(|info| {
|
||||
Some(Rust2024IncompatiblePatSugg {
|
||||
suggest_eliding_modes: info.suggest_eliding_modes,
|
||||
suggestion: Vec::new(),
|
||||
ref_pattern_count: 0,
|
||||
binding_mode_count: 0,
|
||||
labels: labels.as_slice(),
|
||||
}),
|
||||
default_mode_span: None,
|
||||
default_mode_labels: Default::default(),
|
||||
})
|
||||
}),
|
||||
};
|
||||
let result = pcx.lower_pattern(pat);
|
||||
debug!("pat_from_hir({:?}) = {:?}", pat, result);
|
||||
if let Some(sugg) = pcx.rust_2024_migration_suggestion {
|
||||
let mut spans = MultiSpan::from_spans(sugg.labels.iter().map(|(span, _)| *span).collect());
|
||||
for (span, label) in sugg.labels {
|
||||
if let Some(info) = migration_info
|
||||
&& let Some(sugg) = pcx.rust_2024_migration_suggestion
|
||||
{
|
||||
let mut spans =
|
||||
MultiSpan::from_spans(info.primary_labels.iter().map(|(span, _)| *span).collect());
|
||||
for (span, label) in &info.primary_labels {
|
||||
spans.push_span_label(*span, label.clone());
|
||||
}
|
||||
// If a relevant span is from at least edition 2024, this is a hard error.
|
||||
|
@ -70,10 +75,13 @@ pub(super) fn pat_from_hir<'a, 'tcx>(
|
|||
if is_hard_error {
|
||||
let mut err =
|
||||
tcx.dcx().struct_span_err(spans, fluent::mir_build_rust_2024_incompatible_pat);
|
||||
if let Some(info) = lint::builtin::RUST_2024_INCOMPATIBLE_PAT.future_incompatible {
|
||||
if let Some(lint_info) = lint::builtin::RUST_2024_INCOMPATIBLE_PAT.future_incompatible {
|
||||
// provide the same reference link as the lint
|
||||
err.note(format!("for more information, see {}", info.reference));
|
||||
err.note(format!("for more information, see {}", lint_info.reference));
|
||||
}
|
||||
err.arg("bad_modifiers", info.bad_modifiers);
|
||||
err.arg("bad_ref_pats", info.bad_ref_pats);
|
||||
err.arg("is_hard_error", true);
|
||||
err.subdiagnostic(sugg);
|
||||
err.emit();
|
||||
} else {
|
||||
|
@ -81,7 +89,12 @@ pub(super) fn pat_from_hir<'a, 'tcx>(
|
|||
lint::builtin::RUST_2024_INCOMPATIBLE_PAT,
|
||||
pat.hir_id,
|
||||
spans,
|
||||
Rust2024IncompatiblePat { sugg },
|
||||
Rust2024IncompatiblePat {
|
||||
sugg,
|
||||
bad_modifiers: info.bad_modifiers,
|
||||
bad_ref_pats: info.bad_ref_pats,
|
||||
is_hard_error,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +103,35 @@ pub(super) fn pat_from_hir<'a, 'tcx>(
|
|||
|
||||
impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
|
||||
fn lower_pattern(&mut self, pat: &'tcx hir::Pat<'tcx>) -> Box<Pat<'tcx>> {
|
||||
let adjustments: &[Ty<'tcx>] =
|
||||
self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v);
|
||||
|
||||
let mut opt_old_mode_span = None;
|
||||
if let Some(s) = &mut self.rust_2024_migration_suggestion
|
||||
&& !adjustments.is_empty()
|
||||
{
|
||||
let implicit_deref_mutbls = adjustments.iter().map(|ref_ty| {
|
||||
let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
|
||||
span_bug!(pat.span, "pattern implicitly dereferences a non-ref type");
|
||||
};
|
||||
mutbl
|
||||
});
|
||||
|
||||
if !s.suggest_eliding_modes {
|
||||
let suggestion_str: String =
|
||||
implicit_deref_mutbls.clone().map(|mutbl| mutbl.ref_prefix_str()).collect();
|
||||
s.suggestion.push((pat.span.shrink_to_lo(), suggestion_str));
|
||||
s.ref_pattern_count += adjustments.len();
|
||||
}
|
||||
|
||||
// Remember if this changed the default binding mode, in case we want to label it.
|
||||
let min_mutbl = implicit_deref_mutbls.min().unwrap();
|
||||
if s.default_mode_span.is_none_or(|(_, old_mutbl)| min_mutbl < old_mutbl) {
|
||||
opt_old_mode_span = Some(s.default_mode_span);
|
||||
s.default_mode_span = Some((pat.span, min_mutbl));
|
||||
}
|
||||
};
|
||||
|
||||
// When implicit dereferences have been inserted in this pattern, the unadjusted lowered
|
||||
// pattern has the type that results *after* dereferencing. For example, in this code:
|
||||
//
|
||||
|
@ -118,8 +160,6 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
|
|||
_ => self.lower_pattern_unadjusted(pat),
|
||||
};
|
||||
|
||||
let adjustments: &[Ty<'tcx>] =
|
||||
self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v);
|
||||
let adjusted_pat = adjustments.iter().rev().fold(unadjusted_pat, |thir_pat, ref_ty| {
|
||||
debug!("{:?}: wrapping pattern with type {:?}", thir_pat, ref_ty);
|
||||
Box::new(Pat {
|
||||
|
@ -130,24 +170,10 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
|
|||
});
|
||||
|
||||
if let Some(s) = &mut self.rust_2024_migration_suggestion
|
||||
&& !adjustments.is_empty()
|
||||
&& let Some(old_mode_span) = opt_old_mode_span
|
||||
{
|
||||
let suggestion_str: String = adjustments
|
||||
.iter()
|
||||
.map(|ref_ty| {
|
||||
let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
|
||||
span_bug!(pat.span, "pattern implicitly dereferences a non-ref type");
|
||||
};
|
||||
|
||||
match mutbl {
|
||||
ty::Mutability::Not => "&",
|
||||
ty::Mutability::Mut => "&mut ",
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
s.suggestion.push((pat.span.shrink_to_lo(), suggestion_str));
|
||||
s.ref_pattern_count += adjustments.len();
|
||||
};
|
||||
s.default_mode_span = old_mode_span;
|
||||
}
|
||||
|
||||
adjusted_pat
|
||||
}
|
||||
|
@ -340,7 +366,22 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
|
|||
let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
|
||||
PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), mutability }
|
||||
}
|
||||
hir::PatKind::Ref(subpattern, _) | hir::PatKind::Box(subpattern) => {
|
||||
hir::PatKind::Ref(subpattern, _) => {
|
||||
// Track the default binding mode for the Rust 2024 migration suggestion.
|
||||
let old_mode_span = self.rust_2024_migration_suggestion.as_mut().and_then(|s| {
|
||||
if let Some((default_mode_span, default_ref_mutbl)) = s.default_mode_span {
|
||||
// If this eats a by-ref default binding mode, label the binding mode.
|
||||
s.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
|
||||
}
|
||||
s.default_mode_span.take()
|
||||
});
|
||||
let subpattern = self.lower_pattern(subpattern);
|
||||
if let Some(s) = &mut self.rust_2024_migration_suggestion {
|
||||
s.default_mode_span = old_mode_span;
|
||||
}
|
||||
PatKind::Deref { subpattern }
|
||||
}
|
||||
hir::PatKind::Box(subpattern) => {
|
||||
PatKind::Deref { subpattern: self.lower_pattern(subpattern) }
|
||||
}
|
||||
|
||||
|
@ -367,19 +408,32 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
|
|||
.get(pat.hir_id)
|
||||
.expect("missing binding mode");
|
||||
|
||||
if let Some(s) = &mut self.rust_2024_migration_suggestion
|
||||
&& explicit_ba.0 == ByRef::No
|
||||
&& let ByRef::Yes(mutbl) = mode.0
|
||||
{
|
||||
let sugg_str = match mutbl {
|
||||
Mutability::Not => "ref ",
|
||||
Mutability::Mut => "ref mut ",
|
||||
};
|
||||
s.suggestion.push((
|
||||
pat.span.with_lo(ident.span.lo()).shrink_to_lo(),
|
||||
sugg_str.to_owned(),
|
||||
));
|
||||
s.binding_mode_count += 1;
|
||||
if let Some(s) = &mut self.rust_2024_migration_suggestion {
|
||||
if explicit_ba != hir::BindingMode::NONE
|
||||
&& let Some((default_mode_span, default_ref_mutbl)) = s.default_mode_span
|
||||
{
|
||||
// If this overrides a by-ref default binding mode, label the binding mode.
|
||||
s.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
|
||||
// If our suggestion is to elide redundnt modes, this will be one of them.
|
||||
if s.suggest_eliding_modes {
|
||||
s.suggestion.push((pat.span.with_hi(ident.span.lo()), String::new()));
|
||||
s.binding_mode_count += 1;
|
||||
}
|
||||
}
|
||||
if !s.suggest_eliding_modes
|
||||
&& explicit_ba.0 == ByRef::No
|
||||
&& let ByRef::Yes(mutbl) = mode.0
|
||||
{
|
||||
let sugg_str = match mutbl {
|
||||
Mutability::Not => "ref ",
|
||||
Mutability::Mut => "ref mut ",
|
||||
};
|
||||
s.suggestion.push((
|
||||
pat.span.with_lo(ident.span.lo()).shrink_to_lo(),
|
||||
sugg_str.to_owned(),
|
||||
));
|
||||
s.binding_mode_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// A ref x pattern is the same node used for x, and as such it has
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue