Rollup merge of #136817 - dianne:clean-and-comment-pat-migration, r=Nadrieril
Pattern Migration 2024: clean up and comment This follows up on #136577 by moving the pattern migration logic to its own module, removing a bit of unnecessary complexity, and adding comments. Since there's quite a bit of pattern migration logic now (and potentially more in #136496), I think it makes sense to keep it separate from THIR construction, at least as much as is convenient. r? ``@Nadrieril``
This commit is contained in:
commit
767ec0a8ad
3 changed files with 205 additions and 114 deletions
|
@ -1112,9 +1112,6 @@ pub(crate) struct Rust2024IncompatiblePatSugg {
|
|||
pub(crate) suggestion: Vec<(Span, String)>,
|
||||
pub(crate) ref_pattern_count: usize,
|
||||
pub(crate) binding_mode_count: usize,
|
||||
/// 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>,
|
||||
}
|
||||
|
|
182
compiler/rustc_mir_build/src/thir/pattern/migration.rs
Normal file
182
compiler/rustc_mir_build/src/thir/pattern/migration.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
//! Automatic migration of Rust 2021 patterns to a form valid in both Editions 2021 and 2024.
|
||||
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_errors::MultiSpan;
|
||||
use rustc_hir::{BindingMode, ByRef, HirId, Mutability};
|
||||
use rustc_lint as lint;
|
||||
use rustc_middle::span_bug;
|
||||
use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, Ty, TyCtxt};
|
||||
use rustc_span::{Ident, Span};
|
||||
|
||||
use crate::errors::{Rust2024IncompatiblePat, Rust2024IncompatiblePatSugg};
|
||||
use crate::fluent_generated as fluent;
|
||||
|
||||
/// For patterns flagged for migration during HIR typeck, this handles constructing and emitting
|
||||
/// a diagnostic suggestion.
|
||||
pub(super) struct PatMigration<'a> {
|
||||
suggestion: Vec<(Span, String)>,
|
||||
ref_pattern_count: usize,
|
||||
binding_mode_count: usize,
|
||||
/// 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.
|
||||
default_mode_span: Option<(Span, ty::Mutability)>,
|
||||
/// Labels for where incompatibility-causing by-ref default binding modes were introduced.
|
||||
// FIXME(ref_pat_eat_one_layer_2024_structural): To track the default binding mode, we duplicate
|
||||
// logic from HIR typeck (in order to avoid needing to store all changes to the dbm in
|
||||
// TypeckResults). Since the default binding mode acts differently under this feature gate, the
|
||||
// labels will be wrong.
|
||||
default_mode_labels: FxIndexMap<Span, Mutability>,
|
||||
/// Information collected from typeck, including spans for subpatterns invalid in Rust 2024.
|
||||
info: &'a Rust2024IncompatiblePatInfo,
|
||||
}
|
||||
|
||||
impl<'a> PatMigration<'a> {
|
||||
pub(super) fn new(info: &'a Rust2024IncompatiblePatInfo) -> Self {
|
||||
PatMigration {
|
||||
suggestion: Vec::new(),
|
||||
ref_pattern_count: 0,
|
||||
binding_mode_count: 0,
|
||||
default_mode_span: None,
|
||||
default_mode_labels: Default::default(),
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
/// On Rust 2024, this emits a hard error. On earlier Editions, this emits the
|
||||
/// future-incompatibility lint `rust_2024_incompatible_pat`.
|
||||
pub(super) fn emit<'tcx>(self, tcx: TyCtxt<'tcx>, pat_id: HirId) {
|
||||
let mut spans =
|
||||
MultiSpan::from_spans(self.info.primary_labels.iter().map(|(span, _)| *span).collect());
|
||||
for (span, label) in self.info.primary_labels.iter() {
|
||||
spans.push_span_label(*span, label.clone());
|
||||
}
|
||||
let sugg = Rust2024IncompatiblePatSugg {
|
||||
suggest_eliding_modes: self.info.suggest_eliding_modes,
|
||||
suggestion: self.suggestion,
|
||||
ref_pattern_count: self.ref_pattern_count,
|
||||
binding_mode_count: self.binding_mode_count,
|
||||
default_mode_labels: self.default_mode_labels,
|
||||
};
|
||||
// If a relevant span is from at least edition 2024, this is a hard error.
|
||||
let is_hard_error = spans.primary_spans().iter().any(|span| span.at_least_rust_2024());
|
||||
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 {
|
||||
// provide the same reference link as the lint
|
||||
err.note(format!("for more information, see {}", info.reference));
|
||||
}
|
||||
err.arg("bad_modifiers", self.info.bad_modifiers);
|
||||
err.arg("bad_ref_pats", self.info.bad_ref_pats);
|
||||
err.arg("is_hard_error", true);
|
||||
err.subdiagnostic(sugg);
|
||||
err.emit();
|
||||
} else {
|
||||
tcx.emit_node_span_lint(
|
||||
lint::builtin::RUST_2024_INCOMPATIBLE_PAT,
|
||||
pat_id,
|
||||
spans,
|
||||
Rust2024IncompatiblePat {
|
||||
sugg,
|
||||
bad_modifiers: self.info.bad_modifiers,
|
||||
bad_ref_pats: self.info.bad_ref_pats,
|
||||
is_hard_error,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks when we're lowering a pattern that implicitly dereferences the scrutinee.
|
||||
/// This should only be called when the pattern type adjustments list `adjustments` is
|
||||
/// non-empty. Returns the prior default binding mode; this should be followed by a call to
|
||||
/// [`PatMigration::leave_ref`] to restore it when we leave the pattern.
|
||||
pub(super) fn visit_implicit_derefs<'tcx>(
|
||||
&mut self,
|
||||
pat_span: Span,
|
||||
adjustments: &[Ty<'tcx>],
|
||||
) -> Option<(Span, Mutability)> {
|
||||
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 !self.info.suggest_eliding_modes {
|
||||
// If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
|
||||
// fully explicit. i.e. we'll need to suggest reference patterns for this.
|
||||
let suggestion_str: String =
|
||||
implicit_deref_mutbls.clone().map(|mutbl| mutbl.ref_prefix_str()).collect();
|
||||
self.suggestion.push((pat_span.shrink_to_lo(), suggestion_str));
|
||||
self.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 self.default_mode_span.is_none_or(|(_, old_mutbl)| min_mutbl < old_mutbl) {
|
||||
// This changes the default binding mode to `ref` or `ref mut`. Return the old mode so
|
||||
// it can be reinstated when we leave the pattern.
|
||||
self.default_mode_span.replace((pat_span, min_mutbl))
|
||||
} else {
|
||||
// This does not change the default binding mode; it was already `ref` or `ref mut`.
|
||||
self.default_mode_span
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks the default binding mode when we're lowering a `&` or `&mut` pattern.
|
||||
/// Returns the prior default binding mode; this should be followed by a call to
|
||||
/// [`PatMigration::leave_ref`] to restore it when we leave the pattern.
|
||||
pub(super) fn visit_explicit_deref(&mut self) -> Option<(Span, Mutability)> {
|
||||
if let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span {
|
||||
// If this eats a by-ref default binding mode, label the binding mode.
|
||||
self.default_mode_labels.insert(default_mode_span, default_ref_mutbl);
|
||||
}
|
||||
// Set the default binding mode to by-value and return the old default binding mode so it
|
||||
// can be reinstated when we leave the pattern.
|
||||
self.default_mode_span.take()
|
||||
}
|
||||
|
||||
/// Restores the default binding mode after lowering a pattern that could change it.
|
||||
/// This should follow a call to either [`PatMigration::visit_explicit_deref`] or
|
||||
/// [`PatMigration::visit_implicit_derefs`].
|
||||
pub(super) fn leave_ref(&mut self, old_mode_span: Option<(Span, Mutability)>) {
|
||||
self.default_mode_span = old_mode_span
|
||||
}
|
||||
|
||||
/// Determines if a binding is relevant to the diagnostic and adjusts the notes/suggestion if
|
||||
/// so. Bindings are relevant if they have a modifier under a by-ref default mode (invalid in
|
||||
/// Rust 2024) or if we need to suggest a binding modifier for them.
|
||||
pub(super) fn visit_binding(
|
||||
&mut self,
|
||||
pat_span: Span,
|
||||
mode: BindingMode,
|
||||
explicit_ba: BindingMode,
|
||||
ident: Ident,
|
||||
) {
|
||||
if explicit_ba != BindingMode::NONE
|
||||
&& let Some((default_mode_span, default_ref_mutbl)) = self.default_mode_span
|
||||
{
|
||||
// If this overrides a by-ref default binding mode, label the binding mode.
|
||||
self.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 self.info.suggest_eliding_modes {
|
||||
self.suggestion.push((pat_span.with_hi(ident.span.lo()), String::new()));
|
||||
self.binding_mode_count += 1;
|
||||
}
|
||||
}
|
||||
if !self.info.suggest_eliding_modes
|
||||
&& explicit_ba.0 == ByRef::No
|
||||
&& let ByRef::Yes(mutbl) = mode.0
|
||||
{
|
||||
// If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
|
||||
// fully explicit. i.e. we'll need to suggest reference patterns for this.
|
||||
let sugg_str = match mutbl {
|
||||
Mutability::Not => "ref ",
|
||||
Mutability::Mut => "ref mut ",
|
||||
};
|
||||
self.suggestion
|
||||
.push((pat_span.with_lo(ident.span.lo()).shrink_to_lo(), sugg_str.to_owned()));
|
||||
self.binding_mode_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,18 +2,17 @@
|
|||
|
||||
mod check_match;
|
||||
mod const_to_pat;
|
||||
mod migration;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rustc_abi::{FieldIdx, Integer};
|
||||
use rustc_errors::MultiSpan;
|
||||
use rustc_errors::codes::*;
|
||||
use rustc_hir::def::{CtorOf, DefKind, Res};
|
||||
use rustc_hir::pat_util::EnumerateAndAdjustIterator;
|
||||
use rustc_hir::{self as hir, ByRef, Mutability, RangeEnd};
|
||||
use rustc_hir::{self as hir, RangeEnd};
|
||||
use rustc_index::Idx;
|
||||
use rustc_lint as lint;
|
||||
use rustc_middle::mir::interpret::LitToConstInput;
|
||||
use rustc_middle::thir::{
|
||||
Ascription, FieldPat, LocalVarId, Pat, PatKind, PatRange, PatRangeBoundary,
|
||||
|
@ -26,8 +25,8 @@ use rustc_span::{ErrorGuaranteed, Span};
|
|||
use tracing::{debug, instrument};
|
||||
|
||||
pub(crate) use self::check_match::check_match;
|
||||
use self::migration::PatMigration;
|
||||
use crate::errors::*;
|
||||
use crate::fluent_generated as fluent;
|
||||
|
||||
struct PatCtxt<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
|
@ -35,7 +34,7 @@ struct PatCtxt<'a, 'tcx> {
|
|||
typeck_results: &'a ty::TypeckResults<'tcx>,
|
||||
|
||||
/// Used by the Rust 2024 migration lint.
|
||||
rust_2024_migration_suggestion: Option<Rust2024IncompatiblePatSugg>,
|
||||
rust_2024_migration: Option<PatMigration<'a>>,
|
||||
}
|
||||
|
||||
pub(super) fn pat_from_hir<'a, 'tcx>(
|
||||
|
@ -44,59 +43,19 @@ 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: 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,
|
||||
default_mode_span: None,
|
||||
default_mode_labels: Default::default(),
|
||||
})
|
||||
}),
|
||||
rust_2024_migration: typeck_results
|
||||
.rust_2024_migration_desugared_pats()
|
||||
.get(pat.hir_id)
|
||||
.map(PatMigration::new),
|
||||
};
|
||||
let result = pcx.lower_pattern(pat);
|
||||
debug!("pat_from_hir({:?}) = {:?}", pat, result);
|
||||
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.
|
||||
let is_hard_error = spans.primary_spans().iter().any(|span| span.at_least_rust_2024());
|
||||
if is_hard_error {
|
||||
let mut err =
|
||||
tcx.dcx().struct_span_err(spans, fluent::mir_build_rust_2024_incompatible_pat);
|
||||
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 {}", 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 {
|
||||
tcx.emit_node_span_lint(
|
||||
lint::builtin::RUST_2024_INCOMPATIBLE_PAT,
|
||||
pat.hir_id,
|
||||
spans,
|
||||
Rust2024IncompatiblePat {
|
||||
sugg,
|
||||
bad_modifiers: info.bad_modifiers,
|
||||
bad_ref_pats: info.bad_ref_pats,
|
||||
is_hard_error,
|
||||
},
|
||||
);
|
||||
}
|
||||
if let Some(m) = pcx.rust_2024_migration {
|
||||
m.emit(tcx, pat.hir_id);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
@ -106,31 +65,13 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
|
|||
let adjustments: &[Ty<'tcx>] =
|
||||
self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v);
|
||||
|
||||
// Track the default binding mode for the Rust 2024 migration suggestion.
|
||||
let mut opt_old_mode_span = None;
|
||||
if let Some(s) = &mut self.rust_2024_migration_suggestion
|
||||
if let Some(s) = &mut self.rust_2024_migration
|
||||
&& !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));
|
||||
}
|
||||
};
|
||||
opt_old_mode_span = s.visit_implicit_derefs(pat.span, adjustments);
|
||||
}
|
||||
|
||||
// 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:
|
||||
|
@ -169,10 +110,10 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
|
|||
})
|
||||
});
|
||||
|
||||
if let Some(s) = &mut self.rust_2024_migration_suggestion
|
||||
&& let Some(old_mode_span) = opt_old_mode_span
|
||||
if let Some(s) = &mut self.rust_2024_migration
|
||||
&& !adjustments.is_empty()
|
||||
{
|
||||
s.default_mode_span = old_mode_span;
|
||||
s.leave_ref(opt_old_mode_span);
|
||||
}
|
||||
|
||||
adjusted_pat
|
||||
|
@ -368,16 +309,11 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
|
|||
}
|
||||
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 opt_old_mode_span =
|
||||
self.rust_2024_migration.as_mut().and_then(|s| s.visit_explicit_deref());
|
||||
let subpattern = self.lower_pattern(subpattern);
|
||||
if let Some(s) = &mut self.rust_2024_migration_suggestion {
|
||||
s.default_mode_span = old_mode_span;
|
||||
if let Some(s) = &mut self.rust_2024_migration {
|
||||
s.leave_ref(opt_old_mode_span);
|
||||
}
|
||||
PatKind::Deref { subpattern }
|
||||
}
|
||||
|
@ -408,32 +344,8 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
|
|||
.get(pat.hir_id)
|
||||
.expect("missing binding mode");
|
||||
|
||||
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;
|
||||
}
|
||||
if let Some(s) = &mut self.rust_2024_migration {
|
||||
s.visit_binding(pat.span, mode, explicit_ba, ident);
|
||||
}
|
||||
|
||||
// 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