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_supreme::error::ErrorTree<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(mono_args)] = scrutinee_elems {
76            // In this case, we may still have some late-bound generics in `args`, this could ONLY happen for regions
77            assert!(
78                args.is_none()
79                    || args.as_ref().unwrap().len() == args.as_ref().unwrap().regions.len(),
80                "In pattern \"{}\" matching against name \"{}\": we have both monomorphized generics {} and regular generics {}",
81                self,
82                name.with_ctx(&ctx.into_fmt()),
83                mono_args.skip_binder.with_ctx(&ctx.into_fmt()),
84                args.unwrap().with_ctx(&ctx.into_fmt())
85            );
86            // We additionally append the regions from `args` to the monomorphized args, so that we
87            // can match against them. We can ignore the binder because binding levels shouldn't
88            // affect matching.
89            let mut mono_args = mono_args.skip_binder.clone();
90            if let Some(args) = args {
91                // Late-bound regions are appended after the monomorphized ones.
92                mono_args.regions.extend(args.regions);
93            }
94            scrutinee_elems = prefix;
95            args = Some(mono_args);
96        };
97        let args = args.as_ref();
98        // Patterns that start with an impl block match that impl block anywhere. In such a case we
99        // truncate the scrutinee name to start with the rightmost impl in its name. This isn't
100        // fully precise in case of impls within impls, but we'll ignore that.
101        if let Some(PatElem::Impl(_)) = self.elems.first()
102            && let Some((i, _)) = scrutinee_elems
103                .iter()
104                .enumerate()
105                .rfind(|(_, elem)| elem.is_impl())
106        {
107            scrutinee_elems = &scrutinee_elems[i..];
108        }
109
110        let zipped = self.elems.iter().zip_longest(scrutinee_elems).collect_vec();
111        let zipped_len = zipped.len();
112        for (i, x) in zipped.into_iter().enumerate() {
113            let is_last = i + 1 == zipped_len;
114            match x {
115                EitherOrBoth::Both(pat, elem) => {
116                    let args = if is_last { args } else { None };
117                    if !pat.matches_with_generics(ctx, elem, args) {
118                        return false;
119                    }
120                }
121                // The pattern is shorter than the scrutinee and the previous elements match: we
122                // count that as matching.
123                EitherOrBoth::Right(_) => return true,
124                // The pattern is longer than the scrutinee; they don't match.
125                EitherOrBoth::Left(_) => return false,
126            }
127        }
128        // Both had the same length and all the elements matched.
129        true
130    }
131
132    pub fn matches_ty(&self, ctx: &TranslatedCrate, ty: &Ty) -> bool {
133        if let [PatElem::Glob] = self.elems.as_slice() {
134            return true;
135        }
136        match ty.kind() {
137            TyKind::Adt(tref) => {
138                let args = &tref.generics;
139                match tref.id {
140                    TypeId::Adt(type_id) => {
141                        let Some(type_name) = ctx.item_name(type_id) else {
142                            return false;
143                        };
144                        self.matches_with_generics(ctx, type_name, Some(args))
145                    }
146                    TypeId::Builtin(builtin_ty) => {
147                        let name = builtin_ty.get_name();
148                        self.matches_with_generics(ctx, &name, Some(args))
149                    }
150                    TypeId::Tuple => false,
151                }
152            }
153            TyKind::Array(ty, len) => {
154                let type_name = Name::from_path(&["Array"]);
155                let args = GenericArgs {
156                    regions: [].into(),
157                    types: [ty.clone()].into(),
158                    const_generics: [*len.clone()].into(),
159                    trait_refs: [].into(),
160                };
161                self.matches_with_generics(ctx, &type_name, Some(&args))
162            }
163            TyKind::Slice(ty) => {
164                let type_name = Name::from_path(&["Slice"]);
165                let args = GenericArgs {
166                    regions: [].into(),
167                    types: [ty.clone()].into(),
168                    const_generics: [].into(),
169                    trait_refs: [].into(),
170                };
171                self.matches_with_generics(ctx, &type_name, Some(&args))
172            }
173            TyKind::TypeVar(..)
174            | TyKind::Literal(..)
175            | TyKind::Never
176            | TyKind::Ref(..)
177            | TyKind::RawPtr(..)
178            | TyKind::TraitType(..)
179            | TyKind::DynTrait(..)
180            | TyKind::FnPtr(..)
181            | TyKind::FnDef(..)
182            | TyKind::PtrMetadata(..)
183            | TyKind::Error(..) => false,
184        }
185    }
186
187    pub fn matches_const(&self, _ctx: &TranslatedCrate, _c: &ConstantExpr) -> bool {
188        if let [PatElem::Glob] = self.elems.as_slice() {
189            return true;
190        }
191        todo!("non-trivial const generics patterns aren't implemented")
192    }
193
194    /// Compares two patterns that match the same name, in terms of precision. A pattern that is
195    /// fully included in another (i.e. matches a subset of values) is considered "less precise".
196    /// Returns nonsense if the patterns don't match the same name.
197    pub fn compare(&self, other: &Self) -> Ordering {
198        use Ordering::*;
199        use PatElem::*;
200        match self.len().cmp(&other.len()) {
201            o @ (Less | Greater) => return o,
202            _ if self.len() == 0 => return Equal,
203            Equal => {}
204        }
205        match (self.elems.last().unwrap(), other.elems.last().unwrap()) {
206            (Glob, Glob) => Equal,
207            (Glob, _) => Less,
208            (_, Glob) => Greater,
209            // TODO: compare precision of the generics.
210            _ => Equal,
211        }
212    }
213}
214
215/// Orders patterns by precision: the maximal pattern is the most precise. COmparing patterns only
216/// makes sense if they match the same name.
217impl Ord for Pattern {
218    fn cmp(&self, other: &Self) -> Ordering {
219        self.compare(other)
220    }
221}
222impl PartialOrd for Pattern {
223    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
224        Some(self.cmp(other))
225    }
226}
227
228impl PatElem {
229    fn matches_with_generics(
230        &self,
231        ctx: &TranslatedCrate,
232        elem: &PathElem,
233        args: Option<&GenericArgs>,
234    ) -> bool {
235        match (self, elem) {
236            (PatElem::Glob, _) => true,
237            (
238                PatElem::Ident {
239                    name: pat_ident,
240                    generics,
241                    ..
242                },
243                PathElem::Ident(ident, _),
244            ) => {
245                // `crate` is a special keyword that referes to the current crate.
246                let same_ident =
247                    pat_ident == ident || (pat_ident == "crate" && ident == &ctx.crate_name);
248                same_ident && PatTy::matches_generics(ctx, generics, args)
249            }
250            (PatElem::Impl(_pat), PathElem::Impl(ImplElem::Ty(..))) => {
251                // TODO
252                false
253            }
254            (PatElem::Impl(pat), PathElem::Impl(ImplElem::Trait(impl_id))) => {
255                let Some(timpl) = ctx.trait_impls.get(*impl_id) else {
256                    return false;
257                };
258                let Some(trait_name) = ctx.item_name(timpl.impl_trait.id) else {
259                    return false;
260                };
261                pat.matches_with_generics(ctx, trait_name, Some(&timpl.impl_trait.generics))
262            }
263            _ => false,
264        }
265    }
266}
267
268impl PatTy {
269    pub fn matches_generics(
270        ctx: &TranslatedCrate,
271        pats: &[Self],
272        generics: Option<&GenericArgs>,
273    ) -> bool {
274        let Some(generics) = generics else {
275            // If we'r ematching on a plain name without generics info, we ignore pattern generics.
276            return true;
277        };
278        if pats.is_empty() {
279            // If no generics are provided, this counts as a match.
280            return true;
281        }
282        // We don't include regions in patterns.
283        if pats.len() != generics.types.len() + generics.const_generics.len() {
284            return false;
285        }
286        let (type_pats, const_pats) = pats.split_at(generics.types.len());
287        let types_match = generics
288            .types
289            .iter()
290            .zip(type_pats)
291            .all(|(ty, pat)| pat.matches_ty(ctx, ty));
292        let consts_match = generics
293            .const_generics
294            .iter()
295            .zip(const_pats)
296            .all(|(c, pat)| pat.matches_const(ctx, c));
297        types_match && consts_match
298    }
299
300    pub fn matches_ty(&self, ctx: &TranslatedCrate, ty: &Ty) -> bool {
301        match (self, ty.kind()) {
302            (PatTy::Pat(p), _) => p.matches_ty(ctx, ty),
303            (PatTy::Ref(pat_mtbl, p_ty), TyKind::Ref(_, ty, ty_mtbl)) => {
304                pat_mtbl == ty_mtbl && p_ty.matches_ty(ctx, ty)
305            }
306            _ => false,
307        }
308    }
309
310    pub fn matches_const(&self, ctx: &TranslatedCrate, c: &ConstantExpr) -> bool {
311        match self {
312            PatTy::Pat(p) => p.matches_const(ctx, c),
313            PatTy::Ref(..) => false,
314        }
315    }
316}
317
318#[test]
319fn test_compare() {
320    use Ordering::*;
321    let tests = [
322        ("_", Less, "crate"),
323        ("crate::_", Less, "crate::foo"),
324        ("crate::foo", Less, "crate::foo::_"),
325    ];
326    for (x, o, y) in tests {
327        let x = Pattern::parse(x).unwrap();
328        let y = Pattern::parse(y).unwrap();
329        assert_eq!(x.compare(&y), o);
330    }
331}