Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pattern_analysis: gather up place-relevant info #120633

Merged
merged 2 commits into from
Feb 7, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 57 additions & 40 deletions compiler/rustc_pattern_analysis/src/usefulness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,12 +767,6 @@ impl<'a, Cx: TypeCx> PlaceCtxt<'a, Cx> {
fn ctor_arity(&self, ctor: &Constructor<Cx>) -> usize {
self.cx.ctor_arity(ctor, self.ty)
}
fn ctor_sub_tys(
&'a self,
ctor: &'a Constructor<Cx>,
) -> impl Iterator<Item = Cx::Ty> + ExactSizeIterator + Captures<'a> {
self.cx.ctor_sub_tys(ctor, self.ty)
}
fn ctors_for_ty(&self) -> Result<ConstructorSet<Cx>, Cx::Error> {
self.cx.ctors_for_ty(self.ty)
}
Expand Down Expand Up @@ -828,6 +822,38 @@ impl fmt::Display for ValidityConstraint {
}
}

/// Data about a place under investigation.
struct PlaceInfo<Cx: TypeCx> {
/// The type of the place.
ty: Cx::Ty,
/// Whether the place is known to contain valid data.
validity: ValidityConstraint,
/// Whether the place is the scrutinee itself or a subplace of it.
is_scrutinee: bool,
}

impl<Cx: TypeCx> PlaceInfo<Cx> {
fn specialize<'a>(
&'a self,
cx: &'a Cx,
ctor: &'a Constructor<Cx>,
) -> impl Iterator<Item = Self> + ExactSizeIterator + Captures<'a> {
let ctor_sub_tys = cx.ctor_sub_tys(ctor, &self.ty);
let ctor_sub_validity = self.validity.specialize(ctor);
ctor_sub_tys.map(move |ty| PlaceInfo {
ty,
validity: ctor_sub_validity,
is_scrutinee: false,
})
}
}

impl<Cx: TypeCx> Clone for PlaceInfo<Cx> {
fn clone(&self) -> Self {
Self { ty: self.ty.clone(), validity: self.validity, is_scrutinee: self.is_scrutinee }
}
}

/// Represents a pattern-tuple under investigation.
// The three lifetimes are:
// - 'p coming from the input
Expand Down Expand Up @@ -1001,10 +1027,9 @@ struct Matrix<'p, Cx: TypeCx> {
/// each column must have the same type. Each column corresponds to a place within the
/// scrutinee.
rows: Vec<MatrixRow<'p, Cx>>,
/// Track the type of each column/place.
place_ty: SmallVec<[Cx::Ty; 2]>,
/// Track for each column/place whether it contains a known valid value.
place_validity: SmallVec<[ValidityConstraint; 2]>,
/// Track info about each place. Each place corresponds to a column in `rows`, and their types
/// must match.
place_info: SmallVec<[PlaceInfo<Cx>; 2]>,
/// Track whether the virtual wildcard row used to compute exhaustiveness is relevant. See top
/// of the file for details on relevancy.
wildcard_row_is_relevant: bool,
Expand Down Expand Up @@ -1032,10 +1057,10 @@ impl<'p, Cx: TypeCx> Matrix<'p, Cx> {
scrut_ty: Cx::Ty,
scrut_validity: ValidityConstraint,
) -> Self {
let place_info = PlaceInfo { ty: scrut_ty, validity: scrut_validity, is_scrutinee: true };
let mut matrix = Matrix {
rows: Vec::with_capacity(arms.len()),
place_ty: smallvec![scrut_ty],
place_validity: smallvec![scrut_validity],
place_info: smallvec![place_info],
wildcard_row_is_relevant: true,
};
for (row_id, arm) in arms.iter().enumerate() {
Expand All @@ -1051,11 +1076,11 @@ impl<'p, Cx: TypeCx> Matrix<'p, Cx> {
matrix
}

fn head_ty(&self) -> Option<&Cx::Ty> {
self.place_ty.first()
fn head_place(&self) -> Option<&PlaceInfo<Cx>> {
self.place_info.first()
}
fn column_count(&self) -> usize {
self.place_ty.len()
self.place_info.len()
}

fn rows(
Expand Down Expand Up @@ -1083,18 +1108,13 @@ impl<'p, Cx: TypeCx> Matrix<'p, Cx> {
ctor: &Constructor<Cx>,
ctor_is_relevant: bool,
) -> Result<Matrix<'p, Cx>, Cx::Error> {
let ctor_sub_tys = pcx.ctor_sub_tys(ctor);
let arity = ctor_sub_tys.len();
let specialized_place_ty = ctor_sub_tys.chain(self.place_ty[1..].iter().cloned()).collect();
let ctor_sub_validity = self.place_validity[0].specialize(ctor);
let specialized_place_validity = std::iter::repeat(ctor_sub_validity)
.take(arity)
.chain(self.place_validity[1..].iter().copied())
.collect();
let subfield_place_info = self.place_info[0].specialize(pcx.cx, ctor);
let arity = subfield_place_info.len();
let specialized_place_info =
subfield_place_info.chain(self.place_info[1..].iter().cloned()).collect();
let mut matrix = Matrix {
rows: Vec::new(),
place_ty: specialized_place_ty,
place_validity: specialized_place_validity,
place_info: specialized_place_info,
wildcard_row_is_relevant: self.wildcard_row_is_relevant && ctor_is_relevant,
};
for (i, row) in self.rows().enumerate() {
Expand Down Expand Up @@ -1127,11 +1147,11 @@ impl<'p, Cx: TypeCx> fmt::Debug for Matrix<'p, Cx> {
.map(|row| row.iter().map(|pat| format!("{pat:?}")).collect())
.collect();
pretty_printed_matrix
.push(self.place_validity.iter().map(|validity| format!("{validity}")).collect());
.push(self.place_info.iter().map(|place| format!("{}", place.validity)).collect());

let column_count = self.column_count();
assert!(self.rows.iter().all(|row| row.len() == column_count));
assert!(self.place_validity.len() == column_count);
assert!(self.place_info.len() == column_count);
let column_widths: Vec<usize> = (0..column_count)
.map(|col| pretty_printed_matrix.iter().map(|row| row[col].len()).max().unwrap_or(0))
.collect();
Expand Down Expand Up @@ -1432,11 +1452,10 @@ fn collect_overlapping_range_endpoints<'p, Cx: TypeCx>(
/// - unspecialization, where we lift the results from the previous step into results for this step
/// (using `apply_constructor` and by updating `row.useful` for each parent row).
/// This is all explained at the top of the file.
#[instrument(level = "debug", skip(mcx, is_top_level), ret)]
#[instrument(level = "debug", skip(mcx), ret)]
fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
mcx: UsefulnessCtxt<'a, Cx>,
matrix: &mut Matrix<'p, Cx>,
is_top_level: bool,
) -> Result<WitnessMatrix<Cx>, Cx::Error> {
debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count()));

Expand All @@ -1447,7 +1466,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
return Ok(WitnessMatrix::empty());
}

let Some(ty) = matrix.head_ty().cloned() else {
let Some(place) = matrix.head_place() else {
// The base case: there are no columns in the matrix. We are morally pattern-matching on ().
// A row is useful iff it has no (unguarded) rows above it.
let mut useful = true; // Whether the next row is useful.
Expand All @@ -1467,18 +1486,17 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
};
};

debug!("ty: {ty:?}");
let pcx = &PlaceCtxt { cx: mcx.tycx, ty: &ty };
let ty = &place.ty.clone(); // Clone it out so we can mutate `matrix` later.
let pcx = &PlaceCtxt { cx: mcx.tycx, ty };
debug!("ty: {:?}", pcx.ty);
let ctors_for_ty = pcx.ctors_for_ty()?;

// Whether the place/column we are inspecting is known to contain valid data.
let place_validity = matrix.place_validity[0];
// We treat match scrutinees of type `!` or `EmptyEnum` differently.
let is_toplevel_exception =
is_top_level && matches!(ctors_for_ty, ConstructorSet::NoConstructors);
place.is_scrutinee && matches!(ctors_for_ty, ConstructorSet::NoConstructors);
// Whether empty patterns are counted as useful or not. We only warn an empty arm unreachable if
// it is guaranteed unreachable by the opsem (i.e. if the place is `known_valid`).
let empty_arms_are_unreachable = place_validity.is_known_valid()
let empty_arms_are_unreachable = place.validity.is_known_valid()
&& (is_toplevel_exception
|| mcx.tycx.is_exhaustive_patterns_feature_on()
|| mcx.tycx.is_min_exhaustive_patterns_feature_on());
Expand All @@ -1504,7 +1522,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(

// Decide what constructors to report.
let is_integers = matches!(ctors_for_ty, ConstructorSet::Integers { .. });
let always_report_all = is_top_level && !is_integers;
let always_report_all = place.is_scrutinee && !is_integers;
// Whether we should report "Enum::A and Enum::C are missing" or "_ is missing".
let report_individual_missing_ctors = always_report_all || !all_missing;
// Which constructors are considered missing. We ensure that `!missing_ctors.is_empty() =>
Expand All @@ -1525,7 +1543,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
let ctor_is_relevant = matches!(ctor, Constructor::Missing) || missing_ctors.is_empty();
let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor, ctor_is_relevant)?;
let mut witnesses = ensure_sufficient_stack(|| {
compute_exhaustiveness_and_usefulness(mcx, &mut spec_matrix, false)
compute_exhaustiveness_and_usefulness(mcx, &mut spec_matrix)
})?;

// Transform witnesses for `spec_matrix` into witnesses for `matrix`.
Expand Down Expand Up @@ -1600,8 +1618,7 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
) -> Result<UsefulnessReport<'p, Cx>, Cx::Error> {
let cx = UsefulnessCtxt { tycx };
let mut matrix = Matrix::new(arms, scrut_ty, scrut_validity);
let non_exhaustiveness_witnesses =
compute_exhaustiveness_and_usefulness(cx, &mut matrix, true)?;
let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(cx, &mut matrix)?;

let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column();
let arm_usefulness: Vec<_> = arms
Expand Down
Loading