Skip to main content

charon_lib/name_matcher/
mod.rs

1use std::cmp::Ordering;
2
3use itertools::{EitherOrBoth, Itertools};
4use serde::{Deserialize, Serialize};
5
6use crate::{ast::*, formatter::IntoFormatter, pretty::FmtWithCtx};
7
8mod parser;
9
10pub use Pattern as NamePattern;
11
12#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct Pattern {
14    pub elems: Vec<PatElem>,
15}
16
17#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub enum PatElem {
19    /// An identifier, optionally with generic arguments. E.g. `std` or `Box<_>`.
20    Ident {
21        name: String,
22        generics: Vec<PatTy>,
23        /// For pretty-printing only: whether this is the name of a trait.
24        is_trait: bool,
25    },
26    /// An inherent or trait implementation block. For traits, the implemented type is the first
27    /// element of the pattern generics.
28    Impl(Box<Pattern>),
29    /// A `*` or `_`.
30    Glob,
31}
32
33#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub enum PatTy {
35    /// A path, like `my_crate::foo::Type<_, usize>`
36    Pat(Pattern),
37    /// `&T`, `&mut T`
38    Ref(RefKind, Box<Self>),
39}
40
41impl Pattern {
42    pub fn parse(i: &str) -> Result<Self, nom::error::Error<String>> {
43        use std::str::FromStr;
44        Self::from_str(i)
45    }
46    /// Construct a pattern that matches all the impls for that trait.
47    pub fn impl_for(trait_pat: Self) -> Self {
48        Pattern {
49            elems: vec![PatElem::Impl(Box::new(trait_pat))],
50        }
51    }
52
53    fn len(&self) -> usize {
54        self.elems.len()
55    }
56
57    pub fn matches(&self, ctx: &TranslatedCrate, name: &Name) -> bool {
58        self.matches_with_generics(ctx, name, None)
59    }
60
61    pub fn matches_item(&self, ctx: &TranslatedCrate, item: ItemRef<'_>) -> bool {
62        let generics = item.identity_args();
63        let name = &item.item_meta().name;
64        self.matches_with_generics(ctx, name, Some(&generics))
65    }
66
67    pub fn matches_with_generics(
68        &self,
69        ctx: &TranslatedCrate,
70        name: &Name,
71        args: Option<&GenericArgs>,
72    ) -> bool {
73        let mut scrutinee_elems = name.name.as_slice();
74        let mut args: Option<GenericArgs> = args.cloned();
75        if let [prefix @ .., PathElem::Instantiated(instantiation)] = scrutinee_elems {
76            // An `Instantiated` suffix is appended when the generics of an item are modified; it
77            // records the map from the new generics to the old ones.
78            args = match args {
79                None => None,
80                Some(args) if instantiation.params.is_empty() => {
81                    // HACK: Monomorphization doesn't handle late-bound regions properly, so we
82                    // append them here manually.
83                    assert!(
84                        args.len() == args.regions.len(),
85                        "In pattern \"{}\" matching against name \"{}\": we have both monomorphized generics {} and regular generics {}",
86                        self,
87                        name.with_ctx(&ctx.into_fmt()),
88                        instantiation.skip_binder.with_ctx(&ctx.into_fmt()),
89                        args.with_ctx(&ctx.into_fmt())
90                    );
91                    // We can ignore the binder because binding levels shouldn't affect matching.
92                    let mut mono_args = instantiation.skip_binder.clone();
93                    mono_args.regions.extend(args.regions);
94                    Some(mono_args)
95                }
96                Some(args) => {
97                    assert!(
98                        generic_args_match_params(&instantiation.params, &args),
99                        "In pattern \"{}\" matching against name \"{}\": the instantiated item generics {} do not match the item parameters",
100                        self,
101                        name.with_ctx(&ctx.into_fmt()),
102                        args.with_ctx(&ctx.into_fmt())
103                    );
104                    Some(instantiation.as_ref().clone().apply(&args))
105                }
106            };
107            scrutinee_elems = prefix;
108        };
109        let args = args.as_ref();
110        // Patterns that start with an impl block match that impl block anywhere. In such a case we
111        // truncate the scrutinee name to start with the rightmost impl in its name. This isn't
112        // fully precise in case of impls within impls, but we'll ignore that.
113        if let Some(PatElem::Impl(_)) = self.elems.first()
114            && let Some((i, _)) = scrutinee_elems
115                .iter()
116                .enumerate()
117                .rfind(|(_, elem)| elem.is_impl())
118        {
119            scrutinee_elems = &scrutinee_elems[i..];
120        }
121
122        let zipped = self.elems.iter().zip_longest(scrutinee_elems).collect_vec();
123        let zipped_len = zipped.len();
124        for (i, x) in zipped.into_iter().enumerate() {
125            let is_last = i + 1 == zipped_len;
126            match x {
127                EitherOrBoth::Both(pat, elem) => {
128                    let args = if is_last { args } else { None };
129                    if !pat.matches_with_generics(ctx, elem, args) {
130                        return false;
131                    }
132                }
133                // The pattern is shorter than the scrutinee and the previous elements match: we
134                // count that as matching.
135                EitherOrBoth::Right(_) => return true,
136                // The pattern is longer than the scrutinee; they don't match.
137                EitherOrBoth::Left(_) => return false,
138            }
139        }
140        // Both had the same length and all the elements matched.
141        true
142    }
143
144    pub fn matches_ty(&self, ctx: &TranslatedCrate, ty: &Ty) -> bool {
145        if let [PatElem::Glob] = self.elems.as_slice() {
146            return true;
147        }
148        match ty.kind() {
149            TyKind::Adt(tref) => {
150                let name = match tref.as_builtin() {
151                    Some(builtin_ty) => &builtin_ty.get_name(),
152                    None => ctx.item_name(tref.adt_id()),
153                };
154                self.matches_with_generics(ctx, name, Some(&tref.generics))
155            }
156            TyKind::Array(ty, len) => {
157                let type_name = Name::from_path(&["Array"]);
158                let args = GenericArgs {
159                    regions: [].into(),
160                    types: [ty.clone()].into(),
161                    const_generics: [*len.clone()].into(),
162                    trait_refs: [].into(),
163                };
164                self.matches_with_generics(ctx, &type_name, Some(&args))
165            }
166            TyKind::Slice(ty) => {
167                let type_name = Name::from_path(&["Slice"]);
168                let args = GenericArgs {
169                    regions: [].into(),
170                    types: [ty.clone()].into(),
171                    const_generics: [].into(),
172                    trait_refs: [].into(),
173                };
174                self.matches_with_generics(ctx, &type_name, Some(&args))
175            }
176            TyKind::Pattern(ty, _) => self.matches_ty(ctx, ty),
177            TyKind::TypeVar(..)
178            | TyKind::Literal(..)
179            | TyKind::Never
180            | TyKind::Ref(..)
181            | TyKind::RawPtr(..)
182            | TyKind::TraitType(..)
183            | TyKind::DynTrait(..)
184            | TyKind::FnPtr(..)
185            | TyKind::FnDef(..)
186            | TyKind::PtrMetadata(..)
187            | TyKind::Error(..) => false,
188        }
189    }
190
191    pub fn matches_const(&self, _ctx: &TranslatedCrate, _c: &ConstantExpr) -> bool {
192        if let [PatElem::Glob] = self.elems.as_slice() {
193            return true;
194        }
195        todo!("non-trivial const generics patterns aren't implemented")
196    }
197
198    /// Compares two patterns that match the same name, in terms of precision. A pattern that is
199    /// fully included in another (i.e. matches a subset of values) is considered "less precise".
200    /// Returns nonsense if the patterns don't match the same name.
201    pub fn compare(&self, other: &Self) -> Ordering {
202        use Ordering::*;
203        use PatElem::*;
204        match self.len().cmp(&other.len()) {
205            o @ (Less | Greater) => return o,
206            _ if self.len() == 0 => return Equal,
207            Equal => {}
208        }
209        match (self.elems.last().unwrap(), other.elems.last().unwrap()) {
210            (Glob, Glob) => Equal,
211            (Glob, _) => Less,
212            (_, Glob) => Greater,
213            // TODO: compare precision of the generics.
214            _ => Equal,
215        }
216    }
217}
218
219fn generic_args_match_params(params: &GenericParams, args: &GenericArgs) -> bool {
220    params.regions.len() == args.regions.len()
221        && params.types.len() == args.types.len()
222        && params.const_generics.len() == args.const_generics.len()
223        && params.trait_clauses.len() == args.trait_refs.len()
224}
225
226/// Orders patterns by precision: the maximal pattern is the most precise. COmparing patterns only
227/// makes sense if they match the same name.
228impl Ord for Pattern {
229    fn cmp(&self, other: &Self) -> Ordering {
230        self.compare(other)
231    }
232}
233impl PartialOrd for Pattern {
234    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
235        Some(self.cmp(other))
236    }
237}
238
239impl PatElem {
240    fn matches_with_generics(
241        &self,
242        ctx: &TranslatedCrate,
243        elem: &PathElem,
244        args: Option<&GenericArgs>,
245    ) -> bool {
246        match (self, elem) {
247            (PatElem::Glob, _) => true,
248            (
249                PatElem::Ident {
250                    name: pat_ident,
251                    generics,
252                    ..
253                },
254                PathElem::Ident(ident, _),
255            ) => {
256                // `crate` is a special keyword that referes to the current crate.
257                let same_ident =
258                    pat_ident == ident || (pat_ident == "crate" && ident == &ctx.crate_name);
259                same_ident && PatTy::matches_generics(ctx, generics, args)
260            }
261            (PatElem::Impl(_pat), PathElem::Impl(ImplElem::Ty(..))) => {
262                // TODO
263                false
264            }
265            (PatElem::Impl(pat), PathElem::Impl(ImplElem::Trait(impl_id))) => {
266                let Some(timpl) = ctx.trait_impls.get(*impl_id) else {
267                    return false;
268                };
269                let trait_name = ctx.item_name(timpl.impl_trait.id);
270                pat.matches_with_generics(ctx, trait_name, Some(&timpl.impl_trait.generics))
271            }
272            _ => false,
273        }
274    }
275}
276
277impl PatTy {
278    pub fn matches_generics(
279        ctx: &TranslatedCrate,
280        pats: &[Self],
281        generics: Option<&GenericArgs>,
282    ) -> bool {
283        let Some(generics) = generics else {
284            // If we'r ematching on a plain name without generics info, we ignore pattern generics.
285            return true;
286        };
287        if pats.is_empty() {
288            // If no generics are provided, this counts as a match.
289            return true;
290        }
291        // We don't include regions in patterns.
292        if pats.len() != generics.types.len() + generics.const_generics.len() {
293            return false;
294        }
295        let (type_pats, const_pats) = pats.split_at(generics.types.len());
296        let types_match = generics
297            .types
298            .iter()
299            .zip(type_pats)
300            .all(|(ty, pat)| pat.matches_ty(ctx, ty));
301        let consts_match = generics
302            .const_generics
303            .iter()
304            .zip(const_pats)
305            .all(|(c, pat)| pat.matches_const(ctx, c));
306        types_match && consts_match
307    }
308
309    pub fn matches_ty(&self, ctx: &TranslatedCrate, ty: &Ty) -> bool {
310        match (self, ty.kind()) {
311            (PatTy::Pat(p), _) => p.matches_ty(ctx, ty),
312            (PatTy::Ref(pat_mtbl, p_ty), TyKind::Ref(_, ty, ty_mtbl)) => {
313                pat_mtbl == ty_mtbl && p_ty.matches_ty(ctx, ty)
314            }
315            _ => false,
316        }
317    }
318
319    pub fn matches_const(&self, ctx: &TranslatedCrate, c: &ConstantExpr) -> bool {
320        match self {
321            PatTy::Pat(p) => p.matches_const(ctx, c),
322            PatTy::Ref(..) => false,
323        }
324    }
325}
326
327#[test]
328fn test_compare() {
329    use Ordering::*;
330    let tests = [
331        ("_", Less, "crate"),
332        ("crate::_", Less, "crate::foo"),
333        ("crate::foo", Less, "crate::foo::_"),
334    ];
335    for (x, o, y) in tests {
336        let x = Pattern::parse(x).unwrap();
337        let y = Pattern::parse(y).unwrap();
338        assert_eq!(x.compare(&y), o);
339    }
340}