1
Fork 0

Auto merge of #118842 - Nadrieril:librarify-further, r=compiler-errors

Make exhaustiveness usable outside of rustc

With this PR, `rustc_pattern_analysis` compiles on stable (with the `stable` feature)! `rust-analyzer` will be able to use it to provide match-related diagnostics and refactors.

Two questions:
- Should I name the feature `nightly` instead of `rustc` for consistency with other crates? `rustc` makes more sense imo.
- `typed-arena` is an optional dependency but tidy made me add it to the allow-list anyway. Can I avoid that somehow?

r? `@compiler-errors`
This commit is contained in:
bors 2023-12-19 17:15:04 +00:00
commit 3a539c0889
14 changed files with 721 additions and 474 deletions

View file

@ -6,7 +6,7 @@ use rustc_errors::{
};
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
use rustc_middle::ty::{self, Ty};
use rustc_pattern_analysis::{cx::MatchCheckCtxt, errors::Uncovered};
use rustc_pattern_analysis::{errors::Uncovered, rustc::RustcMatchCheckCtxt};
use rustc_span::symbol::Symbol;
use rustc_span::Span;
@ -454,7 +454,7 @@ pub enum UnusedUnsafeEnclosing {
}
pub(crate) struct NonExhaustivePatternsTypeNotEmpty<'p, 'tcx, 'm> {
pub cx: &'m MatchCheckCtxt<'p, 'tcx>,
pub cx: &'m RustcMatchCheckCtxt<'p, 'tcx>,
pub expr_span: Span,
pub span: Span,
pub ty: Ty<'tcx>,

View file

@ -1,13 +1,13 @@
use rustc_pattern_analysis::constructor::Constructor;
use rustc_pattern_analysis::cx::MatchCheckCtxt;
use rustc_pattern_analysis::errors::Uncovered;
use rustc_pattern_analysis::pat::{DeconstructedPat, WitnessPat};
use rustc_pattern_analysis::usefulness::{Usefulness, UsefulnessReport};
use rustc_pattern_analysis::rustc::{
Constructor, DeconstructedPat, RustcMatchCheckCtxt as MatchCheckCtxt, Usefulness,
UsefulnessReport, WitnessPat,
};
use rustc_pattern_analysis::{analyze_match, MatchArm};
use crate::errors::*;
use rustc_arena::TypedArena;
use rustc_arena::{DroplessArena, TypedArena};
use rustc_ast::Mutability;
use rustc_data_structures::fx::FxIndexSet;
use rustc_data_structures::stack::ensure_sufficient_stack;
@ -31,6 +31,7 @@ pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), Err
let (thir, expr) = tcx.thir_body(def_id)?;
let thir = thir.borrow();
let pattern_arena = TypedArena::default();
let dropless_arena = DroplessArena::default();
let mut visitor = MatchVisitor {
tcx,
thir: &*thir,
@ -38,6 +39,7 @@ pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), Err
lint_level: tcx.local_def_id_to_hir_id(def_id),
let_source: LetSource::None,
pattern_arena: &pattern_arena,
dropless_arena: &dropless_arena,
error: Ok(()),
};
visitor.visit_expr(&thir[expr]);
@ -82,6 +84,7 @@ struct MatchVisitor<'thir, 'p, 'tcx> {
lint_level: HirId,
let_source: LetSource,
pattern_arena: &'p TypedArena<DeconstructedPat<'p, 'tcx>>,
dropless_arena: &'p DroplessArena,
/// Tracks if we encountered an error while checking this body. That the first function to
/// report it stores it here. Some functions return `Result` to allow callers to short-circuit
/// on error, but callers don't need to store it here again.
@ -382,6 +385,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
param_env: self.param_env,
module: self.tcx.parent_module(self.lint_level).to_def_id(),
pattern_arena: self.pattern_arena,
dropless_arena: self.dropless_arena,
match_lint_level: self.lint_level,
whole_match_span,
scrut_span,
@ -425,7 +429,8 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
let arm = &self.thir.arms[arm];
let got_error = self.with_lint_level(arm.lint_level, |this| {
let Ok(pat) = this.lower_pattern(&cx, &arm.pattern) else { return true };
let arm = MatchArm { pat, hir_id: this.lint_level, has_guard: arm.guard.is_some() };
let arm =
MatchArm { pat, arm_data: this.lint_level, has_guard: arm.guard.is_some() };
tarms.push(arm);
false
});
@ -548,7 +553,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
) -> Result<(MatchCheckCtxt<'p, 'tcx>, UsefulnessReport<'p, 'tcx>), ErrorGuaranteed> {
let cx = self.new_cx(refutability, None, scrut, pat.span);
let pat = self.lower_pattern(&cx, pat)?;
let arms = [MatchArm { pat, hir_id: self.lint_level, has_guard: false }];
let arms = [MatchArm { pat, arm_data: self.lint_level, has_guard: false }];
let report = analyze_match(&cx, &arms, pat.ty());
Ok((cx, report))
}
@ -847,34 +852,34 @@ fn report_arm_reachability<'p, 'tcx>(
);
};
use Usefulness::*;
let mut catchall = None;
for (arm, is_useful) in report.arm_usefulness.iter() {
match is_useful {
Redundant => report_unreachable_pattern(arm.pat.span(), arm.hir_id, catchall),
Useful(redundant_spans) if redundant_spans.is_empty() => {}
Usefulness::Redundant => {
report_unreachable_pattern(*arm.pat.data(), arm.arm_data, catchall)
}
Usefulness::Useful(redundant_subpats) if redundant_subpats.is_empty() => {}
// The arm is reachable, but contains redundant subpatterns (from or-patterns).
Useful(redundant_spans) => {
let mut redundant_spans = redundant_spans.clone();
Usefulness::Useful(redundant_subpats) => {
let mut redundant_subpats = redundant_subpats.clone();
// Emit lints in the order in which they occur in the file.
redundant_spans.sort_unstable();
for span in redundant_spans {
report_unreachable_pattern(span, arm.hir_id, None);
redundant_subpats.sort_unstable_by_key(|pat| pat.data());
for pat in redundant_subpats {
report_unreachable_pattern(*pat.data(), arm.arm_data, None);
}
}
}
if !arm.has_guard && catchall.is_none() && pat_is_catchall(arm.pat) {
catchall = Some(arm.pat.span());
catchall = Some(*arm.pat.data());
}
}
}
/// Checks for common cases of "catchall" patterns that may not be intended as such.
fn pat_is_catchall(pat: &DeconstructedPat<'_, '_>) -> bool {
use Constructor::*;
match pat.ctor() {
Wildcard => true,
Single => pat.iter_fields().all(|pat| pat_is_catchall(pat)),
Constructor::Wildcard => true,
Constructor::Struct | Constructor::Ref => pat.iter_fields().all(|pat| pat_is_catchall(pat)),
_ => false,
}
}
@ -885,7 +890,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
thir: &Thir<'tcx>,
scrut_ty: Ty<'tcx>,
sp: Span,
witnesses: Vec<WitnessPat<'tcx>>,
witnesses: Vec<WitnessPat<'p, 'tcx>>,
arms: &[ArmId],
expr_span: Span,
) -> ErrorGuaranteed {
@ -1082,10 +1087,10 @@ fn report_non_exhaustive_match<'p, 'tcx>(
fn joined_uncovered_patterns<'p, 'tcx>(
cx: &MatchCheckCtxt<'p, 'tcx>,
witnesses: &[WitnessPat<'tcx>],
witnesses: &[WitnessPat<'p, 'tcx>],
) -> String {
const LIMIT: usize = 3;
let pat_to_str = |pat: &WitnessPat<'tcx>| cx.hoist_witness_pat(pat).to_string();
let pat_to_str = |pat: &WitnessPat<'p, 'tcx>| cx.hoist_witness_pat(pat).to_string();
match witnesses {
[] => bug!(),
[witness] => format!("`{}`", cx.hoist_witness_pat(witness)),
@ -1103,7 +1108,7 @@ fn joined_uncovered_patterns<'p, 'tcx>(
fn collect_non_exhaustive_tys<'tcx>(
cx: &MatchCheckCtxt<'_, 'tcx>,
pat: &WitnessPat<'tcx>,
pat: &WitnessPat<'_, 'tcx>,
non_exhaustive_tys: &mut FxIndexSet<Ty<'tcx>>,
) {
if matches!(pat.ctor(), Constructor::NonExhaustive) {
@ -1122,7 +1127,7 @@ fn collect_non_exhaustive_tys<'tcx>(
fn report_adt_defined_here<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
witnesses: &[WitnessPat<'tcx>],
witnesses: &[WitnessPat<'_, 'tcx>],
point_at_non_local_ty: bool,
) -> Option<AdtDefinedHere<'tcx>> {
let ty = ty.peel_refs();
@ -1144,15 +1149,14 @@ fn report_adt_defined_here<'tcx>(
Some(AdtDefinedHere { adt_def_span, ty, variants })
}
fn maybe_point_at_variant<'a, 'tcx: 'a>(
fn maybe_point_at_variant<'a, 'p: 'a, 'tcx: 'p>(
tcx: TyCtxt<'tcx>,
def: AdtDef<'tcx>,
patterns: impl Iterator<Item = &'a WitnessPat<'tcx>>,
patterns: impl Iterator<Item = &'a WitnessPat<'p, 'tcx>>,
) -> Vec<Span> {
use Constructor::*;
let mut covered = vec![];
for pattern in patterns {
if let Variant(variant_index) = pattern.ctor() {
if let Constructor::Variant(variant_index) = pattern.ctor() {
if let ty::Adt(this_def, _) = pattern.ty().kind()
&& this_def.did() != def.did()
{