Skip to content

Commit

Permalink
Introduce MatchCtxt
Browse files Browse the repository at this point in the history
  • Loading branch information
Nadrieril committed Dec 15, 2023
1 parent 60ea14b commit 4bcf66f
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 77 deletions.
9 changes: 5 additions & 4 deletions compiler/rustc_pattern_analysis/src/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ impl<Cx: MatchCx> Constructor<Cx> {
/// The number of fields for this constructor. This must be kept in sync with
/// `Fields::wildcards`.
pub(crate) fn arity(&self, pcx: &PlaceCtxt<'_, '_, Cx>) -> usize {
pcx.cx.ctor_arity(self, pcx.ty)
pcx.ctor_arity(self)
}

/// Returns whether `self` is covered by `other`, i.e. whether `self` is a subset of `other`.
Expand All @@ -729,7 +729,8 @@ impl<Cx: MatchCx> Constructor<Cx> {
pub(crate) fn is_covered_by<'p>(&self, pcx: &PlaceCtxt<'_, 'p, Cx>, other: &Self) -> bool {
match (self, other) {
(Wildcard, _) => pcx
.cx
.mcx
.tycx
.bug(format_args!("Constructor splitting should not have returned `Wildcard`")),
// Wildcards cover anything
(_, Wildcard) => true,
Expand Down Expand Up @@ -771,7 +772,7 @@ impl<Cx: MatchCx> Constructor<Cx> {
(Opaque(self_id), Opaque(other_id)) => self_id == other_id,
(Opaque(..), _) | (_, Opaque(..)) => false,

_ => pcx.cx.bug(format_args!(
_ => pcx.mcx.tycx.bug(format_args!(
"trying to compare incompatible constructors {self:?} and {other:?}"
)),
}
Expand Down Expand Up @@ -1007,7 +1008,7 @@ impl<Cx: MatchCx> ConstructorSet<Cx> {
// We have now grouped all the constructors into 3 buckets: present, missing, missing_empty.
// In the absence of the `exhaustive_patterns` feature however, we don't count nested empty
// types as empty. Only non-nested `!` or `enum Foo {}` are considered empty.
if !pcx.cx.is_exhaustive_patterns_feature_on()
if !pcx.mcx.tycx.is_exhaustive_patterns_feature_on()
&& !(pcx.is_scrutinee && matches!(self, Self::NoConstructors))
{
// Treat all missing constructors as nonempty.
Expand Down
46 changes: 34 additions & 12 deletions compiler/rustc_pattern_analysis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ use crate::rustc::RustcMatchCheckCtxt;
#[cfg(feature = "rustc")]
use crate::usefulness::{compute_match_usefulness, ValidityConstraint};

// It's not possible to only enable the `typed_arena` dependency when the `rustc` feature is off, so
// we use another feature instead. The crate won't compile if one of these isn't enabled.
#[cfg(feature = "rustc")]
pub(crate) use rustc_arena::TypedArena;
#[cfg(feature = "stable")]
pub(crate) use typed_arena::Arena as TypedArena;

pub trait Captures<'a> {}
impl<'a, T: ?Sized> Captures<'a> for T {}

/// Context that provides type information about constructors.
///
/// Most of the crate is parameterized on a type that implements this trait.
pub trait MatchCx: Sized + Clone + fmt::Debug {
/// The type of a pattern.
type Ty: Copy + Clone + fmt::Debug; // FIXME: remove Copy
Expand Down Expand Up @@ -71,42 +84,51 @@ pub trait MatchCx: Sized + Clone + fmt::Debug {
fn bug(&self, fmt: fmt::Arguments<'_>) -> !;
}

/// Context that provides information global to a match.
#[derive(Clone)]
pub struct MatchCtxt<'a, 'p, Cx: MatchCx> {
/// The context for type information.
pub tycx: &'a Cx,
/// An arena to store the wildcards we produce during analysis.
pub wildcard_arena: &'a TypedArena<DeconstructedPat<'p, Cx>>,
}

impl<'a, 'p, Cx: MatchCx> Copy for MatchCtxt<'a, 'p, Cx> {}

/// The arm of a match expression.
#[derive(Clone, Debug)]
pub struct MatchArm<'p, Cx: MatchCx> {
/// The pattern must have been lowered through `check_match::MatchVisitor::lower_pattern`.
pub pat: &'p DeconstructedPat<'p, Cx>,
pub has_guard: bool,
pub arm_data: Cx::ArmData,
}

impl<'p, Cx: MatchCx> Copy for MatchArm<'p, Cx> {}

pub trait Captures<'a> {}
impl<'a, T: ?Sized> Captures<'a> for T {}

/// The entrypoint for this crate. Computes whether a match is exhaustive and which of its arms are
/// useful, and runs some lints.
#[cfg(feature = "rustc")]
pub fn analyze_match<'p, 'tcx>(
cx: &RustcMatchCheckCtxt<'p, 'tcx>,
tycx: &RustcMatchCheckCtxt<'p, 'tcx>,
arms: &[rustc::MatchArm<'p, 'tcx>],
scrut_ty: Ty<'tcx>,
) -> rustc::UsefulnessReport<'p, 'tcx> {
// Arena to store the extra wildcards we construct during analysis.
let wildcard_arena = cx.pattern_arena;
let pat_column = PatternColumn::new(arms);
let wildcard_arena = tycx.pattern_arena;
let scrut_validity = ValidityConstraint::from_bool(tycx.known_valid_scrutinee);
let cx = MatchCtxt { tycx, wildcard_arena };

let report = compute_match_usefulness(cx, arms, scrut_ty, scrut_validity);

let scrut_validity = ValidityConstraint::from_bool(cx.known_valid_scrutinee);
let report = compute_match_usefulness(cx, arms, scrut_ty, scrut_validity, wildcard_arena);
let pat_column = PatternColumn::new(arms);

// Lint on ranges that overlap on their endpoints, which is likely a mistake.
lint_overlapping_range_endpoints(cx, &pat_column, wildcard_arena);
lint_overlapping_range_endpoints(cx, &pat_column);

// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
if cx.refutable && report.non_exhaustiveness_witnesses.is_empty() {
lint_nonexhaustive_missing_variants(cx, arms, &pat_column, scrut_ty, wildcard_arena)
if tycx.refutable && report.non_exhaustiveness_witnesses.is_empty() {
lint_nonexhaustive_missing_variants(cx, arms, &pat_column, scrut_ty)
}

report
Expand Down
56 changes: 27 additions & 29 deletions compiler/rustc_pattern_analysis/src/lints.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use rustc_arena::TypedArena;
use smallvec::SmallVec;

use rustc_data_structures::captures::Captures;
Expand All @@ -13,8 +12,8 @@ use crate::errors::{
OverlappingRangeEndpoints, Uncovered,
};
use crate::rustc::{
Constructor, DeconstructedPat, MatchArm, PlaceCtxt, RustcMatchCheckCtxt, SplitConstructorSet,
WitnessPat,
Constructor, DeconstructedPat, MatchArm, MatchCtxt, PlaceCtxt, RustcMatchCheckCtxt,
SplitConstructorSet, WitnessPat,
};
use crate::MatchCx;

Expand Down Expand Up @@ -70,7 +69,7 @@ impl<'a, 'p, 'tcx> PatternColumn<'a, 'p, 'tcx> {
/// Do constructor splitting on the constructors of the column.
fn analyze_ctors(&self, pcx: &PlaceCtxt<'_, 'p, 'tcx>) -> SplitConstructorSet<'p, 'tcx> {
let column_ctors = self.patterns.iter().map(|p| p.ctor());
pcx.cx.ctors_for_ty(pcx.ty).split(pcx, column_ctors)
pcx.ctors_for_ty().split(pcx, column_ctors)
}

fn iter<'b>(&'b self) -> impl Iterator<Item = &'a DeconstructedPat<'p, 'tcx>> + Captures<'b> {
Expand Down Expand Up @@ -121,16 +120,15 @@ impl<'a, 'p, 'tcx> PatternColumn<'a, 'p, 'tcx> {

/// Traverse the patterns to collect any variants of a non_exhaustive enum that fail to be mentioned
/// in a given column.
#[instrument(level = "debug", skip(cx, wildcard_arena), ret)]
#[instrument(level = "debug", skip(cx), ret)]
fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
cx: &RustcMatchCheckCtxt<'p, 'tcx>,
cx: MatchCtxt<'a, 'p, 'tcx>,
column: &PatternColumn<'a, 'p, 'tcx>,
wildcard_arena: &TypedArena<DeconstructedPat<'p, 'tcx>>,
) -> Vec<WitnessPat<'p, 'tcx>> {
let Some(ty) = column.head_ty() else {
return Vec::new();
};
let pcx = &PlaceCtxt::new_dummy(cx, ty, wildcard_arena);
let pcx = &PlaceCtxt::new_dummy(cx, ty);

let set = column.analyze_ctors(pcx);
if set.present.is_empty() {
Expand All @@ -141,7 +139,7 @@ fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
}

let mut witnesses = Vec::new();
if cx.is_foreign_non_exhaustive_enum(ty) {
if cx.tycx.is_foreign_non_exhaustive_enum(ty) {
witnesses.extend(
set.missing
.into_iter()
Expand All @@ -157,7 +155,7 @@ fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
let wild_pat = WitnessPat::wild_from_ctor(pcx, ctor);
for (i, col_i) in specialized_columns.iter().enumerate() {
// Compute witnesses for each column.
let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i, wildcard_arena);
let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i);
// For each witness, we build a new pattern in the shape of `ctor(_, _, wit, _, _)`,
// adding enough wildcards to match `arity`.
for wit in wits_for_col_i {
Expand All @@ -171,29 +169,29 @@ fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
}

pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
cx: &RustcMatchCheckCtxt<'p, 'tcx>,
cx: MatchCtxt<'a, 'p, 'tcx>,
arms: &[MatchArm<'p, 'tcx>],
pat_column: &PatternColumn<'a, 'p, 'tcx>,
scrut_ty: Ty<'tcx>,
wildcard_arena: &TypedArena<DeconstructedPat<'p, 'tcx>>,
) {
let rcx: &RustcMatchCheckCtxt<'_, '_> = cx.tycx;
if !matches!(
cx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, cx.match_lint_level).0,
rcx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, rcx.match_lint_level).0,
rustc_session::lint::Level::Allow
) {
let witnesses = collect_nonexhaustive_missing_variants(cx, pat_column, wildcard_arena);
let witnesses = collect_nonexhaustive_missing_variants(cx, pat_column);
if !witnesses.is_empty() {
// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
// is not exhaustive enough.
//
// NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`.
cx.tcx.emit_spanned_lint(
rcx.tcx.emit_spanned_lint(
NON_EXHAUSTIVE_OMITTED_PATTERNS,
cx.match_lint_level,
cx.scrut_span,
rcx.match_lint_level,
rcx.scrut_span,
NonExhaustiveOmittedPattern {
scrut_ty,
uncovered: Uncovered::new(cx.scrut_span, cx, witnesses),
uncovered: Uncovered::new(rcx.scrut_span, rcx, witnesses),
},
);
}
Expand All @@ -203,17 +201,17 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
// usage of the lint.
for arm in arms {
let (lint_level, lint_level_source) =
cx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, arm.arm_data);
rcx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, arm.arm_data);
if !matches!(lint_level, rustc_session::lint::Level::Allow) {
let decorator = NonExhaustiveOmittedPatternLintOnArm {
lint_span: lint_level_source.span(),
suggest_lint_on_match: cx.whole_match_span.map(|span| span.shrink_to_lo()),
suggest_lint_on_match: rcx.whole_match_span.map(|span| span.shrink_to_lo()),
lint_level: lint_level.as_str(),
lint_name: "non_exhaustive_omitted_patterns",
};

use rustc_errors::DecorateLint;
let mut err = cx.tcx.sess.struct_span_warn(*arm.pat.data(), "");
let mut err = rcx.tcx.sess.struct_span_warn(*arm.pat.data(), "");
err.set_primary_message(decorator.msg());
decorator.decorate_lint(&mut err);
err.emit();
Expand All @@ -223,30 +221,30 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
}

/// Traverse the patterns to warn the user about ranges that overlap on their endpoints.
#[instrument(level = "debug", skip(cx, wildcard_arena))]
#[instrument(level = "debug", skip(cx))]
pub(crate) fn lint_overlapping_range_endpoints<'a, 'p, 'tcx>(
cx: &RustcMatchCheckCtxt<'p, 'tcx>,
cx: MatchCtxt<'a, 'p, 'tcx>,
column: &PatternColumn<'a, 'p, 'tcx>,
wildcard_arena: &TypedArena<DeconstructedPat<'p, 'tcx>>,
) {
let Some(ty) = column.head_ty() else {
return;
};
let pcx = &PlaceCtxt::new_dummy(cx, ty, wildcard_arena);
let pcx = &PlaceCtxt::new_dummy(cx, ty);
let rcx: &RustcMatchCheckCtxt<'_, '_> = cx.tycx;

let set = column.analyze_ctors(pcx);

if matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_)) {
let emit_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| {
let overlap_as_pat = cx.hoist_pat_range(overlap, ty);
let overlap_as_pat = rcx.hoist_pat_range(overlap, ty);
let overlaps: Vec<_> = overlapped_spans
.iter()
.copied()
.map(|span| Overlap { range: overlap_as_pat.clone(), span })
.collect();
cx.tcx.emit_spanned_lint(
rcx.tcx.emit_spanned_lint(
lint::builtin::OVERLAPPING_RANGE_ENDPOINTS,
cx.match_lint_level,
rcx.match_lint_level,
this_span,
OverlappingRangeEndpoints { overlap: overlaps, range: this_span },
);
Expand Down Expand Up @@ -291,7 +289,7 @@ pub(crate) fn lint_overlapping_range_endpoints<'a, 'p, 'tcx>(
// Recurse into the fields.
for ctor in set.present {
for col in column.specialize(pcx, &ctor) {
lint_overlapping_range_endpoints(cx, &col, wildcard_arena);
lint_overlapping_range_endpoints(cx, &col);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_pattern_analysis/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ impl<'p, Cx: MatchCx> DeconstructedPat<'p, Cx> {
other_ctor: &Constructor<Cx>,
) -> SmallVec<[&'a DeconstructedPat<'p, Cx>; 2]> {
let wildcard_sub_tys = || {
let tys = pcx.cx.ctor_sub_tys(other_ctor, pcx.ty);
let tys = pcx.ctor_sub_tys(other_ctor);
tys.iter()
.map(|ty| DeconstructedPat::wildcard(*ty, Cx::PatData::default()))
.map(|pat| pcx.wildcard_arena.alloc(pat) as &_)
.map(|pat| pcx.mcx.wildcard_arena.alloc(pat) as &_)
.collect()
};
match (&self.ctor, other_ctor) {
Expand Down Expand Up @@ -179,7 +179,7 @@ impl<Cx: MatchCx> WitnessPat<Cx> {
/// For example, if `ctor` is a `Constructor::Variant` for `Option::Some`, we get the pattern
/// `Some(_)`.
pub(crate) fn wild_from_ctor(pcx: &PlaceCtxt<'_, '_, Cx>, ctor: Constructor<Cx>) -> Self {
let field_tys = pcx.cx.ctor_sub_tys(&ctor, pcx.ty);
let field_tys = pcx.ctor_sub_tys(&ctor);
let fields = field_tys.iter().map(|ty| Self::wildcard(*ty)).collect();
Self::new(ctor, fields, pcx.ty)
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_pattern_analysis/src/rustc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub type ConstructorSet<'p, 'tcx> =
pub type DeconstructedPat<'p, 'tcx> =
crate::pat::DeconstructedPat<'p, RustcMatchCheckCtxt<'p, 'tcx>>;
pub type MatchArm<'p, 'tcx> = crate::MatchArm<'p, RustcMatchCheckCtxt<'p, 'tcx>>;
pub type MatchCtxt<'a, 'p, 'tcx> = crate::MatchCtxt<'a, 'p, RustcMatchCheckCtxt<'p, 'tcx>>;
pub(crate) type PlaceCtxt<'a, 'p, 'tcx> =
crate::usefulness::PlaceCtxt<'a, 'p, RustcMatchCheckCtxt<'p, 'tcx>>;
pub(crate) type SplitConstructorSet<'p, 'tcx> =
Expand Down
Loading

0 comments on commit 4bcf66f

Please sign in to comment.