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

Exhaustiveness: abort on type error #119715

Merged
merged 4 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
7 changes: 5 additions & 2 deletions compiler/rustc_mir_build/src/thir/pattern/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,10 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> {
}

let scrut_ty = scrut.ty;
let report = analyze_match(&cx, &tarms, scrut_ty);
let Ok(report) = analyze_match(&cx, &tarms, scrut_ty).map_err(|err| self.error = Err(err))
else {
return;
};
Nadrieril marked this conversation as resolved.
Show resolved Hide resolved

match source {
// Don't report arm reachability of desugared `match $iter.into_iter() { iter => .. }`
Expand Down Expand Up @@ -544,7 +547,7 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> {
let cx = self.new_cx(refutability, None, scrut, pat.span);
let pat = self.lower_pattern(&cx, pat)?;
let arms = [MatchArm { pat, arm_data: self.lint_level, has_guard: false }];
let report = analyze_match(&cx, &arms, pat.ty().inner());
let report = analyze_match(&cx, &arms, pat.ty().inner())?;
Ok((cx, report))
}

Expand Down
16 changes: 10 additions & 6 deletions compiler/rustc_pattern_analysis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ use std::fmt;
use rustc_index::Idx;
#[cfg(feature = "rustc")]
use rustc_middle::ty::Ty;
#[cfg(feature = "rustc")]
use rustc_span::ErrorGuaranteed;

use crate::constructor::{Constructor, ConstructorSet};
#[cfg(feature = "rustc")]
Expand Down Expand Up @@ -52,6 +54,8 @@ impl<'a, T: ?Sized> Captures<'a> for T {}
pub trait TypeCx: Sized + fmt::Debug {
/// The type of a pattern.
type Ty: Copy + Clone + fmt::Debug; // FIXME: remove Copy
/// Errors that can abort analysis.
type Error: fmt::Debug;
/// The index of an enum variant.
type VariantIdx: Clone + Idx;
/// A string literal
Expand All @@ -73,7 +77,7 @@ pub trait TypeCx: Sized + fmt::Debug {
/// The set of all the constructors for `ty`.
///
/// This must follow the invariants of `ConstructorSet`
fn ctors_for_ty(&self, ty: Self::Ty) -> ConstructorSet<Self>;
fn ctors_for_ty(&self, ty: Self::Ty) -> Result<ConstructorSet<Self>, Self::Error>;

/// Best-effort `Debug` implementation.
fn debug_pat(f: &mut fmt::Formatter<'_>, pat: &DeconstructedPat<'_, Self>) -> fmt::Result;
Expand Down Expand Up @@ -109,25 +113,25 @@ pub fn analyze_match<'p, 'tcx>(
tycx: &RustcMatchCheckCtxt<'p, 'tcx>,
arms: &[rustc::MatchArm<'p, 'tcx>],
scrut_ty: Ty<'tcx>,
) -> rustc::UsefulnessReport<'p, 'tcx> {
) -> Result<rustc::UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
// Arena to store the extra wildcards we construct during analysis.
let wildcard_arena = tycx.pattern_arena;
let scrut_ty = tycx.reveal_opaque_ty(scrut_ty);
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 report = compute_match_usefulness(cx, arms, scrut_ty, scrut_validity)?;

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);
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 tycx.refutable && report.non_exhaustiveness_witnesses.is_empty() {
lint_nonexhaustive_missing_variants(cx, arms, &pat_column, scrut_ty)
lint_nonexhaustive_missing_variants(cx, arms, &pat_column, scrut_ty)?;
}

report
Ok(report)
}
36 changes: 21 additions & 15 deletions compiler/rustc_pattern_analysis/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use rustc_data_structures::captures::Captures;
use rustc_middle::ty;
use rustc_session::lint;
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
use rustc_span::Span;
use rustc_span::{ErrorGuaranteed, Span};

use crate::constructor::{IntRange, MaybeInfiniteInt};
use crate::errors::{
Expand Down Expand Up @@ -52,9 +52,13 @@ impl<'p, 'tcx> PatternColumn<'p, 'tcx> {
}

/// Do constructor splitting on the constructors of the column.
fn analyze_ctors(&self, pcx: &PlaceCtxt<'_, 'p, 'tcx>) -> SplitConstructorSet<'p, 'tcx> {
fn analyze_ctors(
&self,
pcx: &PlaceCtxt<'_, 'p, 'tcx>,
) -> Result<SplitConstructorSet<'p, 'tcx>, ErrorGuaranteed> {
let column_ctors = self.patterns.iter().map(|p| p.ctor());
pcx.ctors_for_ty().split(pcx, column_ctors)
let ctors_for_ty = &pcx.ctors_for_ty()?;
Ok(ctors_for_ty.split(pcx, column_ctors))
}

fn iter(&self) -> impl Iterator<Item = &'p DeconstructedPat<'p, 'tcx>> + Captures<'_> {
Expand Down Expand Up @@ -110,18 +114,18 @@ impl<'p, 'tcx> PatternColumn<'p, 'tcx> {
fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
cx: MatchCtxt<'a, 'p, 'tcx>,
column: &PatternColumn<'p, 'tcx>,
) -> Vec<WitnessPat<'p, 'tcx>> {
) -> Result<Vec<WitnessPat<'p, 'tcx>>, ErrorGuaranteed> {
let Some(ty) = column.head_ty() else {
return Vec::new();
return Ok(Vec::new());
};
let pcx = &PlaceCtxt::new_dummy(cx, ty);

let set = column.analyze_ctors(pcx);
let set = column.analyze_ctors(pcx)?;
if set.present.is_empty() {
// We can't consistently handle the case where no constructors are present (since this would
// require digging deep through any type in case there's a non_exhaustive enum somewhere),
// so for consistency we refuse to handle the top-level case, where we could handle it.
return vec![];
return Ok(Vec::new());
}

let mut witnesses = Vec::new();
Expand All @@ -141,7 +145,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);
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 @@ -151,21 +155,21 @@ fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
}
}
}
witnesses
Ok(witnesses)
}

pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
cx: MatchCtxt<'a, 'p, 'tcx>,
arms: &[MatchArm<'p, 'tcx>],
pat_column: &PatternColumn<'p, 'tcx>,
scrut_ty: RevealedTy<'tcx>,
) {
) -> Result<(), ErrorGuaranteed> {
let rcx: &RustcMatchCheckCtxt<'_, '_> = cx.tycx;
if !matches!(
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);
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.
Expand Down Expand Up @@ -204,21 +208,22 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
}
}
}
Ok(())
}

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

let set = column.analyze_ctors(pcx);
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]| {
Expand Down Expand Up @@ -275,8 +280,9 @@ 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);
lint_overlapping_range_endpoints(cx, &col)?;
}
}
}
Ok(())
}
19 changes: 15 additions & 4 deletions compiler/rustc_pattern_analysis/src/rustc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use rustc_middle::mir::interpret::Scalar;
use rustc_middle::mir::{self, Const};
use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange, PatRangeBoundary};
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::TypeVisitableExt;
use rustc_middle::ty::{self, OpaqueTypeKey, Ty, TyCtxt, VariantDef};
use rustc_span::ErrorGuaranteed;
use rustc_span::{Span, DUMMY_SP};
use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT};
use smallvec::SmallVec;
Expand Down Expand Up @@ -302,7 +304,10 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> {
///
/// See [`crate::constructor`] for considerations of emptiness.
#[instrument(level = "debug", skip(self), ret)]
pub fn ctors_for_ty(&self, ty: RevealedTy<'tcx>) -> ConstructorSet<'p, 'tcx> {
pub fn ctors_for_ty(
&self,
ty: RevealedTy<'tcx>,
) -> Result<ConstructorSet<'p, 'tcx>, ErrorGuaranteed> {
let cx = self;
let make_uint_range = |start, end| {
IntRange::from_range(
Expand All @@ -311,9 +316,11 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> {
RangeEnd::Included,
)
};
// Abort on type error.
ty.error_reported()?;
compiler-errors marked this conversation as resolved.
Show resolved Hide resolved
// This determines the set of all possible constructors for the type `ty`. For numbers,
// arrays and slices we use ranges and variable-length slices when appropriate.
match ty.kind() {
Ok(match ty.kind() {
ty::Bool => ConstructorSet::Bool,
ty::Char => {
// The valid Unicode Scalar Value ranges.
Expand Down Expand Up @@ -423,7 +430,7 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> {
ty::CoroutineWitness(_, _) | ty::Bound(_, _) | ty::Placeholder(_) | ty::Infer(_) => {
bug!("Encountered unexpected type in `ConstructorSet::for_ty`: {ty:?}")
}
}
})
}

pub(crate) fn lower_pat_range_bdy(
Expand Down Expand Up @@ -944,6 +951,7 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> {

impl<'p, 'tcx> TypeCx for RustcMatchCheckCtxt<'p, 'tcx> {
type Ty = RevealedTy<'tcx>;
type Error = ErrorGuaranteed;
type VariantIdx = VariantIdx;
type StrLit = Const<'tcx>;
type ArmData = HirId;
Expand All @@ -963,7 +971,10 @@ impl<'p, 'tcx> TypeCx for RustcMatchCheckCtxt<'p, 'tcx> {
) -> &[Self::Ty] {
self.ctor_sub_tys(ctor, ty)
}
fn ctors_for_ty(&self, ty: Self::Ty) -> crate::constructor::ConstructorSet<Self> {
fn ctors_for_ty(
&self,
ty: Self::Ty,
) -> Result<crate::constructor::ConstructorSet<Self>, Self::Error> {
self.ctors_for_ty(ty)
}

Expand Down
25 changes: 13 additions & 12 deletions compiler/rustc_pattern_analysis/src/usefulness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ impl<'a, 'p, Cx: TypeCx> PlaceCtxt<'a, 'p, Cx> {
pub(crate) fn ctor_sub_tys(&self, ctor: &Constructor<Cx>) -> &[Cx::Ty] {
self.mcx.tycx.ctor_sub_tys(ctor, self.ty)
}
pub(crate) fn ctors_for_ty(&self) -> ConstructorSet<Cx> {
pub(crate) fn ctors_for_ty(&self) -> Result<ConstructorSet<Cx>, Cx::Error> {
self.mcx.tycx.ctors_for_ty(self.ty)
}
}
Expand Down Expand Up @@ -1341,14 +1341,14 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
mcx: MatchCtxt<'a, 'p, Cx>,
matrix: &mut Matrix<'p, Cx>,
is_top_level: bool,
) -> WitnessMatrix<Cx> {
) -> Result<WitnessMatrix<Cx>, Cx::Error> {
debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count()));

if !matrix.wildcard_row_is_relevant && matrix.rows().all(|r| !r.pats.relevant) {
// Here we know that nothing will contribute further to exhaustiveness or usefulness. This
// is purely an optimization: skipping this check doesn't affect correctness. See the top of
// the file for details.
return WitnessMatrix::empty();
return Ok(WitnessMatrix::empty());
}

let Some(ty) = matrix.head_ty() else {
Expand All @@ -1360,16 +1360,16 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
// When there's an unguarded row, the match is exhaustive and any subsequent row is not
// useful.
if !row.is_under_guard {
return WitnessMatrix::empty();
return Ok(WitnessMatrix::empty());
}
}
// No (unguarded) rows, so the match is not exhaustive. We return a new witness unless
// irrelevant.
return if matrix.wildcard_row_is_relevant {
WitnessMatrix::unit_witness()
Ok(WitnessMatrix::unit_witness())
} else {
// We choose to not report anything here; see at the top for details.
WitnessMatrix::empty()
Ok(WitnessMatrix::empty())
};
};

Expand All @@ -1383,7 +1383,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(

// Analyze the constructors present in this column.
let ctors = matrix.heads().map(|p| p.ctor());
let ctors_for_ty = pcx.ctors_for_ty();
let ctors_for_ty = pcx.ctors_for_ty()?;
let is_integers = matches!(ctors_for_ty, ConstructorSet::Integers { .. }); // For diagnostics.
let split_set = ctors_for_ty.split(pcx, ctors);
let all_missing = split_set.present.is_empty();
Expand Down Expand Up @@ -1422,7 +1422,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
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)
});
})?;

// Transform witnesses for `spec_matrix` into witnesses for `matrix`.
witnesses.apply_constructor(pcx, &missing_ctors, &ctor, report_individual_missing_ctors);
Expand All @@ -1443,7 +1443,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
}
}

ret
Ok(ret)
}

/// Indicates whether or not a given arm is useful.
Expand Down Expand Up @@ -1474,9 +1474,10 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
arms: &[MatchArm<'p, Cx>],
scrut_ty: Cx::Ty,
scrut_validity: ValidityConstraint,
) -> UsefulnessReport<'p, Cx> {
) -> Result<UsefulnessReport<'p, Cx>, Cx::Error> {
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, true)?;

let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column();
let arm_usefulness: Vec<_> = arms
Expand All @@ -1493,5 +1494,5 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
(arm, usefulness)
})
.collect();
UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses }
Ok(UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses })
}
13 changes: 13 additions & 0 deletions tests/ui/pattern/usefulness/issue-119493-type-error-ice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
fn main() {}

fn foo() {
#[derive(Copy, Clone)]
struct Foo(NonExistent);
//~^ ERROR cannot find type
//~| ERROR cannot find type

type U = impl Copy;
//~^ ERROR `impl Trait` in type aliases is unstable
let foo: U = Foo(());
let Foo(()) = foo;
}
30 changes: 30 additions & 0 deletions tests/ui/pattern/usefulness/issue-119493-type-error-ice.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
error[E0412]: cannot find type `NonExistent` in this scope
--> $DIR/issue-119493-type-error-ice.rs:5:16
|
LL | struct Foo(NonExistent);
| ^^^^^^^^^^^ not found in this scope

error[E0412]: cannot find type `NonExistent` in this scope
--> $DIR/issue-119493-type-error-ice.rs:5:16
|
LL | struct Foo(NonExistent);
| ^^^^^^^^^^^ not found in this scope
|
help: you might be missing a type parameter
|
LL | struct Foo<NonExistent>(NonExistent);
| +++++++++++++

error[E0658]: `impl Trait` in type aliases is unstable
--> $DIR/issue-119493-type-error-ice.rs:9:14
|
LL | type U = impl Copy;
| ^^^^^^^^^
|
= note: see issue #63063 <https://github.com/rust-lang/rust/issues/63063> for more information
= help: add `#![feature(type_alias_impl_trait)]` to the crate attributes to enable

error: aborting due to 3 previous errors

Some errors have detailed explanations: E0412, E0658.
For more information about an error, try `rustc --explain E0412`.
Loading