Add {ast,hir,thir}::PatKind::Missing variants.

"Missing" patterns are possible in bare fn types (`fn f(u32)`) and
similar places. Currently these are represented in the AST with
`ast::PatKind::Ident` with no `by_ref`, no `mut`, an empty ident, and no
sub-pattern. This flows through to `{hir,thir}::PatKind::Binding` for
HIR and THIR.

This is a bit nasty. It's very non-obvious, and easy to forget to check
for the exceptional empty identifier case.

This commit adds a new variant, `PatKind::Missing`, to do it properly.

The process I followed:
- Add a `Missing` variant to `{ast,hir,thir}::PatKind`.
- Chang `parse_param_general` to produce `ast::PatKind::Missing`
  instead of `ast::PatKind::Missing`.
- Look through `kw::Empty` occurrences to find functions where an
  existing empty ident check needs replacing with a `PatKind::Missing`
  check: `print_param`, `check_trait_item`, `is_named_param`.
- Add a `PatKind::Missing => unreachable!(),` arm to every exhaustive
  match identified by the compiler.
- Find which arms are actually reachable by running the test suite,
  changing them to something appropriate, usually by looking at what
  would happen to a `PatKind::Ident`/`PatKind::Binding` with no ref, no
  `mut`, an empty ident, and no subpattern.

Quite a few of the `unreachable!()` arms were never reached. This makes
sense because `PatKind::Missing` can't happen in every pattern, only
in places like bare fn tys and trait fn decls.

I also tried an alternative approach: modifying `ast::Param::pat` to
hold an `Option<P<Pat>>` instead of a `P<Pat>`, but that quickly turned
into a very large and painful change. Adding `PatKind::Missing` is much
easier.
This commit is contained in:
Nicholas Nethercote 2025-03-27 09:33:02 +11:00
parent 217693a1f0
commit 9f089e080c
40 changed files with 86 additions and 46 deletions

View file

@ -45,6 +45,7 @@ fn unary_pattern(pat: &Pat<'_>) -> bool {
pats.iter().all(unary_pattern)
}
match &pat.kind {
PatKind::Missing => unreachable!(),
PatKind::Slice(_, _, _)
| PatKind::Range(_, _, _)
| PatKind::Binding(..)

View file

@ -253,6 +253,7 @@ fn iter_matching_struct_fields<'a>(
impl<'a> NormalizedPat<'a> {
fn from_pat(cx: &LateContext<'_>, arena: &'a DroplessArena, pat: &'a Pat<'_>) -> Self {
match pat.kind {
PatKind::Missing => unreachable!(),
PatKind::Wild | PatKind::Binding(.., None) => Self::Wild,
PatKind::Binding(.., Some(pat))
| PatKind::Box(pat)

View file

@ -406,6 +406,7 @@ impl<'a> PatState<'a> {
pats.iter().map(|p| p.pat),
),
PatKind::Missing => unreachable!(),
PatKind::Wild
| PatKind::Binding(_, _, _, None)
| PatKind::Expr(_)

View file

@ -224,6 +224,7 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec<P<Pat>>, focus_idx: us
// We're trying to find whatever kind (~"constructor") we found in `alternatives[start..]`.
let changed = match &mut focus_kind {
Missing => unreachable!(),
// These pattern forms are "leafs" and do not have sub-patterns.
// Therefore they are not some form of constructor `C`,
// with which a pattern `C(p_0)` may be formed,

View file

@ -676,6 +676,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
}
match pat.value.kind {
PatKind::Missing => unreachable!(),
PatKind::Wild => kind!("Wild"),
PatKind::Never => kind!("Never"),
PatKind::Binding(ann, _, name, sub) => {

View file

@ -33,6 +33,7 @@ pub fn eq_id(l: Ident, r: Ident) -> bool {
pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
use PatKind::*;
match (&l.kind, &r.kind) {
(Missing, _) | (_, Missing) => unreachable!(),
(Paren(l), _) => eq_pat(l, r),
(_, Paren(r)) => eq_pat(l, r),
(Wild, Wild) | (Rest, Rest) => true,

View file

@ -1124,6 +1124,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
pub fn hash_pat(&mut self, pat: &Pat<'_>) {
std::mem::discriminant(&pat.kind).hash(&mut self.s);
match pat.kind {
PatKind::Missing => unreachable!(),
PatKind::Binding(BindingMode(by_ref, mutability), _, _, pat) => {
std::mem::discriminant(&by_ref).hash(&mut self.s);
std::mem::discriminant(&mutability).hash(&mut self.s);

View file

@ -1857,6 +1857,7 @@ pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
}
match pat.kind {
PatKind::Missing => unreachable!(),
PatKind::Wild | PatKind::Never => false, // If `!` typechecked then the type is empty, so not refutable.
PatKind::Binding(_, _, _, pat) => pat.is_some_and(|pat| is_refutable(cx, pat)),
PatKind::Box(pat) | PatKind::Ref(pat, _) => is_refutable(cx, pat),