Skip to main content

hir_ide/diagnostics/
match_check.rs

1//! Validation of matches.
2//!
3//! This module provides lowering from [hir_def::hir::Pat] to [self::Pat] and match
4//! checking algorithm.
5//!
6//! It is modeled on the rustc module `rustc_mir_build::thir::pattern`.
7
8mod pat_util;
9
10pub(crate) mod pat_analysis;
11
12use hir_def::{
13    AdtId, EnumVariantId, LocalFieldId, Lookup, VariantId,
14    expr_store::{Body, path::Path},
15    hir::PatId,
16    item_tree::FieldsShape,
17    signatures::{StructSignature, UnionSignature},
18};
19use hir_expand::name::Name;
20use rustc_type_ir::inherent::IntoKind;
21use span::Edition;
22use stdx::{always, never, variance::PhantomCovariantLifetime};
23
24use crate::{
25    BindingMode, ByRef, InferenceResult,
26    db::HirDatabase,
27    display::{HirDisplay, HirDisplayError, HirFormatter},
28    next_solver::{GenericArgs, Mutability, Ty, TyKind},
29};
30
31use self::pat_util::EnumerateAndAdjustIterator;
32
33#[derive(Clone, Debug)]
34pub(crate) enum PatternError {
35    Unimplemented,
36    UnexpectedType,
37    UnresolvedVariant,
38    MissingField,
39    ExtraFields,
40}
41
42#[derive(Clone, Debug, PartialEq)]
43pub(crate) struct FieldPat<'db> {
44    pub(crate) field: LocalFieldId,
45    pub(crate) pattern: Pat<'db>,
46}
47
48#[derive(Clone, Debug, PartialEq)]
49pub(crate) struct Pat<'db> {
50    pub(crate) ty: Ty<'db>,
51    pub(crate) kind: Box<PatKind<'db>>,
52}
53
54/// Close relative to `rustc_mir_build::thir::pattern::PatKind`
55#[derive(Clone, Debug, PartialEq)]
56pub(crate) enum PatKind<'db> {
57    Wild,
58    Never,
59
60    /// `x`, `ref x`, `x @ P`, etc.
61    Binding {
62        name: Name,
63        subpattern: Option<Pat<'db>>,
64    },
65
66    /// `Foo(...)` or `Foo{...}` or `Foo`, where `Foo` is a variant name from an ADT with
67    /// multiple variants.
68    Variant {
69        substs: GenericArgs<'db>,
70        enum_variant: EnumVariantId,
71        subpatterns: Vec<FieldPat<'db>>,
72    },
73
74    /// `(...)`, `Foo(...)`, `Foo{...}`, or `Foo`, where `Foo` is a variant name from an ADT with
75    /// a single variant.
76    Leaf {
77        subpatterns: Vec<FieldPat<'db>>,
78    },
79
80    /// `&P`, `&mut P`, etc.
81    Deref {
82        subpattern: Pat<'db>,
83    },
84
85    // FIXME: for now, only bool literals are implemented
86    LiteralBool {
87        value: bool,
88    },
89
90    /// An or-pattern, e.g. `p | q`.
91    /// Invariant: `pats.len() >= 2`.
92    Or {
93        pats: Vec<Pat<'db>>,
94    },
95}
96
97pub(crate) struct PatCtxt<'a, 'db> {
98    db: &'db dyn HirDatabase,
99    infer: &'db InferenceResult<'db>,
100    body: &'a Body,
101    pub(crate) errors: Vec<PatternError>,
102}
103
104impl<'a, 'db> PatCtxt<'a, 'db> {
105    pub(crate) fn new(
106        db: &'db dyn HirDatabase,
107        infer: &'db InferenceResult<'db>,
108        body: &'a Body,
109    ) -> Self {
110        Self { db, infer, body, errors: Vec::new() }
111    }
112
113    pub(crate) fn lower_pattern(&mut self, pat: PatId) -> Pat<'db> {
114        // XXX(iDawer): Collecting pattern adjustments feels imprecise to me.
115        // When lowering of & and box patterns are implemented this should be tested
116        // in a manner of `match_ergonomics_issue_9095` test.
117        // Pattern adjustment is part of RFC 2005-match-ergonomics.
118        // More info https://github.com/rust-lang/rust/issues/42640#issuecomment-313535089
119        let unadjusted_pat = self.lower_pattern_unadjusted(pat);
120        self.infer.pat_adjustments.get(&pat).map(|it| &**it).unwrap_or_default().iter().rev().fold(
121            unadjusted_pat,
122            |subpattern, ref_ty| Pat {
123                ty: ref_ty.source.as_ref(),
124                kind: Box::new(PatKind::Deref { subpattern }),
125            },
126        )
127    }
128
129    fn lower_pattern_unadjusted(&mut self, pat: PatId) -> Pat<'db> {
130        let mut ty = self.infer.pat_ty(pat);
131        let variant = self.infer.variant_resolution_for_pat(pat);
132
133        let kind = match self.body[pat] {
134            hir_def::hir::Pat::Wild => PatKind::Wild,
135
136            hir_def::hir::Pat::Lit(expr) => self.lower_lit(expr),
137
138            hir_def::hir::Pat::Path(ref path) => {
139                return self.lower_path(pat, path);
140            }
141
142            hir_def::hir::Pat::Tuple { ref args, ellipsis } => {
143                let arity = match ty.kind() {
144                    TyKind::Tuple(tys) => tys.len(),
145                    _ => {
146                        never!("unexpected type for tuple pattern: {:?}", ty);
147                        self.errors.push(PatternError::UnexpectedType);
148                        return Pat { ty, kind: PatKind::Wild.into() };
149                    }
150                };
151                let subpatterns = self.lower_tuple_subpats(args, arity, ellipsis);
152                PatKind::Leaf { subpatterns }
153            }
154
155            hir_def::hir::Pat::Bind { id, subpat, .. } => {
156                let bm = self.infer.binding_modes[pat];
157                ty = self.infer.binding_ty(id);
158                let name = &self.body[id].name;
159                match (bm, ty.kind()) {
160                    (BindingMode(ByRef::Yes(_), _), TyKind::Ref(_, rty, _)) => ty = rty,
161                    (BindingMode(ByRef::Yes(_), _), _) => {
162                        never!(
163                            "`ref {}` has wrong type {:?}",
164                            name.display(self.db, Edition::LATEST),
165                            ty
166                        );
167                        self.errors.push(PatternError::UnexpectedType);
168                        return Pat { ty, kind: PatKind::Wild.into() };
169                    }
170                    _ => (),
171                }
172                PatKind::Binding { name: name.clone(), subpattern: self.lower_opt_pattern(subpat) }
173            }
174
175            hir_def::hir::Pat::TupleStruct { ref args, ellipsis, .. } if variant.is_some() => {
176                let expected_len = variant.unwrap().fields(self.db).fields().len();
177                let subpatterns = self.lower_tuple_subpats(args, expected_len, ellipsis);
178                self.lower_variant_or_leaf(pat, ty, subpatterns)
179            }
180
181            hir_def::hir::Pat::Record { ref args, .. } if variant.is_some() => {
182                let variant_data = variant.unwrap().fields(self.db);
183                let subpatterns = args
184                    .iter()
185                    .map(|field| {
186                        // XXX(iDawer): field lookup is inefficient
187                        variant_data.field(&field.name).map(|lfield_id| FieldPat {
188                            field: lfield_id,
189                            pattern: self.lower_pattern(field.pat),
190                        })
191                    })
192                    .collect();
193                match subpatterns {
194                    Some(subpatterns) => self.lower_variant_or_leaf(pat, ty, subpatterns),
195                    None => {
196                        self.errors.push(PatternError::MissingField);
197                        PatKind::Wild
198                    }
199                }
200            }
201            hir_def::hir::Pat::TupleStruct { .. } | hir_def::hir::Pat::Record { .. } => {
202                self.errors.push(PatternError::UnresolvedVariant);
203                PatKind::Wild
204            }
205
206            hir_def::hir::Pat::Or(ref pats) => PatKind::Or { pats: self.lower_patterns(pats) },
207
208            _ => {
209                self.errors.push(PatternError::Unimplemented);
210                PatKind::Wild
211            }
212        };
213
214        Pat { ty, kind: Box::new(kind) }
215    }
216
217    fn lower_tuple_subpats(
218        &mut self,
219        pats: &[PatId],
220        expected_len: usize,
221        ellipsis: Option<u32>,
222    ) -> Vec<FieldPat<'db>> {
223        if pats.len() > expected_len {
224            self.errors.push(PatternError::ExtraFields);
225            return Vec::new();
226        }
227
228        pats.iter()
229            .enumerate_and_adjust(expected_len, ellipsis.map(|it| it as usize))
230            .map(|(i, &subpattern)| FieldPat {
231                field: LocalFieldId::from_raw((i as u32).into()),
232                pattern: self.lower_pattern(subpattern),
233            })
234            .collect()
235    }
236
237    fn lower_patterns(&mut self, pats: &[PatId]) -> Vec<Pat<'db>> {
238        pats.iter().map(|&p| self.lower_pattern(p)).collect()
239    }
240
241    fn lower_opt_pattern(&mut self, pat: Option<PatId>) -> Option<Pat<'db>> {
242        pat.map(|p| self.lower_pattern(p))
243    }
244
245    fn lower_variant_or_leaf(
246        &mut self,
247        pat: PatId,
248        ty: Ty<'db>,
249        subpatterns: Vec<FieldPat<'db>>,
250    ) -> PatKind<'db> {
251        match self.infer.variant_resolution_for_pat(pat) {
252            Some(variant_id) => {
253                if let VariantId::EnumVariantId(enum_variant) = variant_id {
254                    let substs = match ty.kind() {
255                        TyKind::Adt(_, substs) => substs,
256                        kind => {
257                            always!(
258                                matches!(kind, TyKind::FnDef(..) | TyKind::Error(_)),
259                                "inappropriate type for def: {:?}",
260                                ty
261                            );
262                            self.errors.push(PatternError::UnexpectedType);
263                            return PatKind::Wild;
264                        }
265                    };
266                    PatKind::Variant { substs, enum_variant, subpatterns }
267                } else {
268                    PatKind::Leaf { subpatterns }
269                }
270            }
271            None => {
272                self.errors.push(PatternError::UnresolvedVariant);
273                PatKind::Wild
274            }
275        }
276    }
277
278    fn lower_path(&mut self, pat: PatId, _path: &Path) -> Pat<'db> {
279        let ty = self.infer.pat_ty(pat);
280
281        let pat_from_kind = |kind| Pat { ty, kind: Box::new(kind) };
282
283        match self.infer.variant_resolution_for_pat(pat) {
284            Some(_) => pat_from_kind(self.lower_variant_or_leaf(pat, ty, Vec::new())),
285            None => {
286                self.errors.push(PatternError::UnresolvedVariant);
287                pat_from_kind(PatKind::Wild)
288            }
289        }
290    }
291
292    fn lower_lit(&mut self, expr: hir_def::hir::ExprId) -> PatKind<'db> {
293        use hir_def::hir::{Expr, Literal::Bool};
294
295        match self.body[expr] {
296            Expr::Literal(Bool(value)) => PatKind::LiteralBool { value },
297            _ => {
298                self.errors.push(PatternError::Unimplemented);
299                PatKind::Wild
300            }
301        }
302    }
303}
304
305impl<'db> HirDisplay<'db> for Pat<'db> {
306    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result<(), HirDisplayError> {
307        match &*self.kind {
308            PatKind::Wild => write!(f, "_"),
309            PatKind::Never => write!(f, "!"),
310            PatKind::Binding { name, subpattern } => {
311                write!(f, "{}", name.display(f.db, f.edition()))?;
312                if let Some(subpattern) = subpattern {
313                    write!(f, " @ ")?;
314                    subpattern.hir_fmt(f)?;
315                }
316                Ok(())
317            }
318            PatKind::Variant { subpatterns, .. } | PatKind::Leaf { subpatterns } => {
319                let variant = match *self.kind {
320                    PatKind::Variant { enum_variant, .. } => Some(VariantId::from(enum_variant)),
321                    _ => self.ty.as_adt().and_then(|(adt, _)| match adt {
322                        AdtId::StructId(s) => Some(s.into()),
323                        AdtId::UnionId(u) => Some(u.into()),
324                        AdtId::EnumId(_) => None,
325                    }),
326                };
327
328                if let Some(variant) = variant {
329                    match variant {
330                        VariantId::EnumVariantId(v) => {
331                            let loc = v.lookup(f.db);
332                            write!(f, "{}", loc.name.display(f.db, f.edition()))?;
333                        }
334                        VariantId::StructId(s) => write!(
335                            f,
336                            "{}",
337                            StructSignature::of(f.db, s).name.display(f.db, f.edition())
338                        )?,
339                        VariantId::UnionId(u) => write!(
340                            f,
341                            "{}",
342                            UnionSignature::of(f.db, u).name.display(f.db, f.edition())
343                        )?,
344                    };
345
346                    let variant_data = variant.fields(f.db);
347                    if variant_data.shape == FieldsShape::Record {
348                        write!(f, " {{ ")?;
349
350                        let mut printed = 0;
351                        let subpats = subpatterns
352                            .iter()
353                            .filter(|p| !matches!(*p.pattern.kind, PatKind::Wild))
354                            .map(|p| {
355                                printed += 1;
356                                WriteWith::new(|f| {
357                                    write!(
358                                        f,
359                                        "{}: ",
360                                        variant_data.fields()[p.field]
361                                            .name
362                                            .display(f.db, f.edition())
363                                    )?;
364                                    p.pattern.hir_fmt(f)
365                                })
366                            });
367                        f.write_joined(subpats, ", ")?;
368
369                        if printed < variant_data.fields().len() {
370                            write!(f, "{}..", if printed > 0 { ", " } else { "" })?;
371                        }
372
373                        return write!(f, " }}");
374                    }
375                }
376
377                let num_fields =
378                    variant.map_or(subpatterns.len(), |v| v.fields(f.db).fields().len());
379                if num_fields != 0 || variant.is_none() {
380                    write!(f, "(")?;
381                    let subpats = (0..num_fields).map(|i| {
382                        WriteWith::new(move |f| {
383                            let fid = LocalFieldId::from_raw((i as u32).into());
384                            if let Some(p) = subpatterns.get(i)
385                                && p.field == fid
386                            {
387                                return p.pattern.hir_fmt(f);
388                            }
389                            if let Some(p) = subpatterns.iter().find(|p| p.field == fid) {
390                                p.pattern.hir_fmt(f)
391                            } else {
392                                write!(f, "_")
393                            }
394                        })
395                    });
396                    f.write_joined(subpats, ", ")?;
397                    if let (TyKind::Tuple(..), 1) = (self.ty.kind(), num_fields) {
398                        write!(f, ",")?;
399                    }
400                    write!(f, ")")?;
401                }
402
403                Ok(())
404            }
405            PatKind::Deref { subpattern } => {
406                match self.ty.kind() {
407                    TyKind::Ref(.., mutbl) => {
408                        write!(f, "&{}", if mutbl == Mutability::Mut { "mut " } else { "" })?
409                    }
410                    _ => never!("{:?} is a bad Deref pattern type", self.ty),
411                }
412                subpattern.hir_fmt(f)
413            }
414            PatKind::LiteralBool { value } => write!(f, "{value}"),
415            PatKind::Or { pats } => f.write_joined(pats.iter(), " | "),
416        }
417    }
418}
419
420struct WriteWith<'db, F>(F, PhantomCovariantLifetime<'db>)
421where
422    F: Fn(&mut HirFormatter<'_, 'db>) -> Result<(), HirDisplayError>;
423
424impl<'db, F> WriteWith<'db, F>
425where
426    F: Fn(&mut HirFormatter<'_, 'db>) -> Result<(), HirDisplayError>,
427{
428    fn new(f: F) -> Self {
429        Self(f, PhantomCovariantLifetime::new())
430    }
431}
432
433impl<'db, F> HirDisplay<'db> for WriteWith<'db, F>
434where
435    F: Fn(&mut HirFormatter<'_, 'db>) -> Result<(), HirDisplayError>,
436{
437    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result<(), HirDisplayError> {
438        (self.0)(f)
439    }
440}