Skip to main content

charon_lib/name_matcher/
mod.rs

1use std::cmp::Ordering;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{ast::*, formatter::IntoFormatter, pretty::FmtWithCtx};
6
7mod parser;
8
9pub use Pattern as NamePattern;
10
11#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct Pattern {
13    pub elems: Vec<PatElem>,
14}
15
16#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub enum PatElem {
18    /// An identifier, optionally with generic arguments. E.g. `std` or `Box<_>`.
19    Ident {
20        name: String,
21        generics: Vec<PatTy>,
22        /// For pretty-printing only: whether this is the name of a trait.
23        is_trait: bool,
24    },
25    /// An inherent or trait implementation block. For traits, the implemented type is the first
26    /// element of the pattern generics.
27    Impl(Box<Pattern>),
28    /// A `*` or `_`.
29    Glob,
30}
31
32#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub enum PatTy {
34    /// A path, like `my_crate::foo::Type<_, usize>`
35    Pat(Pattern),
36    /// `&T`, `&mut T`
37    Ref(RefKind, Box<Self>),
38}
39
40impl Pattern {
41    pub fn parse(i: &str) -> Result<Self, nom::error::Error<String>> {
42        use std::str::FromStr;
43        Self::from_str(i)
44    }
45    /// Construct a pattern that matches all the impls for that trait.
46    pub fn impl_for(trait_pat: Self) -> Self {
47        Pattern {
48            elems: vec![PatElem::Impl(Box::new(trait_pat))],
49        }
50    }
51
52    fn len(&self) -> usize {
53        self.elems.len()
54    }
55
56    pub fn matches(&self, ctx: &TranslatedCrate, name: &Name) -> bool {
57        self.matches_with_generics(ctx, name, None)
58    }
59
60    pub fn matches_item(&self, ctx: &TranslatedCrate, item: ItemRef<'_>) -> bool {
61        let generics = item.identity_args();
62        let name = &item.item_meta().name;
63        self.matches_with_generics(ctx, name, Some(&generics))
64    }
65
66    pub fn matches_with_generics(
67        &self,
68        ctx: &TranslatedCrate,
69        name: &Name,
70        args: Option<&GenericArgs>,
71    ) -> bool {
72        let mut scrutinee_elems = name.name.as_slice();
73        let instantiated_args: Option<GenericArgs>;
74        let mut args: Option<&GenericArgs> = args;
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            instantiated_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.iter().cloned());
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            args = instantiated_args.as_ref();
108            scrutinee_elems = prefix;
109        };
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        // The pattern is longer than the scrutinee; they don't match.
123        if self.elems.len() > scrutinee_elems.len() {
124            return false;
125        }
126        // The pattern is shorter or equal to the scrutinee: it matches if their shared
127        // prefix matches.
128        let same_length = self.elems.len() == scrutinee_elems.len();
129        for (i, pat) in self.elems.iter().enumerate() {
130            let is_last = same_length && i + 1 == self.elems.len();
131            let args = if is_last { args } else { None };
132            if !pat.matches_with_generics(ctx, &scrutinee_elems[i], args) {
133                return false;
134            }
135        }
136        true
137    }
138
139    pub fn matches_ty(&self, ctx: &TranslatedCrate, ty: &Ty) -> bool {
140        if let [PatElem::Glob] = self.elems.as_slice() {
141            return true;
142        }
143        match ty.kind() {
144            TyKind::Adt(tref) => {
145                let type_name = ctx.item_name(tref.id);
146                self.matches_with_generics(ctx, type_name, Some(&tref.generics))
147            }
148            TyKind::Array(ty, len, _) => {
149                let type_name = Name::from_path(&["Array"]);
150                let args = GenericArgs {
151                    regions: [].into(),
152                    types: [ty.clone()].into(),
153                    const_generics: [len.clone()].into(),
154                    trait_refs: [].into(),
155                };
156                self.matches_with_generics(ctx, &type_name, Some(&args))
157            }
158            TyKind::Slice(ty, _) => {
159                let type_name = Name::from_path(&["Slice"]);
160                let args = GenericArgs {
161                    regions: [].into(),
162                    types: [ty.clone()].into(),
163                    const_generics: [].into(),
164                    trait_refs: [].into(),
165                };
166                self.matches_with_generics(ctx, &type_name, Some(&args))
167            }
168            TyKind::Pattern(ty, _) => self.matches_ty(ctx, ty),
169            TyKind::Scalar(ty) => matches!(
170                self.elems.as_slice(),
171                [PatElem::Ident { name, generics, .. }]
172                    if generics.is_empty() && name == &ty.to_string()
173            ),
174            TyKind::TypeVar(..)
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
215fn generic_args_match_params(params: &GenericParams, args: &GenericArgs) -> bool {
216    params.regions.len() == args.regions.len()
217        && params.types.len() == args.types.len()
218        && params.const_generics.len() == args.const_generics.len()
219        && params.trait_clauses.len() == args.trait_refs.len()
220}
221
222/// Orders patterns by precision: the maximal pattern is the most precise. COmparing patterns only
223/// makes sense if they match the same name.
224impl Ord for Pattern {
225    fn cmp(&self, other: &Self) -> Ordering {
226        self.compare(other)
227    }
228}
229impl PartialOrd for Pattern {
230    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
231        Some(self.cmp(other))
232    }
233}
234
235impl PatElem {
236    fn matches_with_generics(
237        &self,
238        ctx: &TranslatedCrate,
239        elem: &PathElem,
240        args: Option<&GenericArgs>,
241    ) -> bool {
242        match (self, elem) {
243            (PatElem::Glob, _) => true,
244            (
245                PatElem::Ident {
246                    name: pat_ident,
247                    generics,
248                    ..
249                },
250                PathElem::Ident(ident, _),
251            ) => {
252                // `crate` is a special keyword that referes to the current crate.
253                let same_ident =
254                    pat_ident == ident || (pat_ident == "crate" && ident == &ctx.crate_name);
255                same_ident && PatTy::matches_generics(ctx, generics, args)
256            }
257            (
258                PatElem::Ident {
259                    name: pat_ident,
260                    generics,
261                    ..
262                },
263                PathElem::Builtin(builtin, _),
264            ) => {
265                !builtin.is_tuple()
266                    && pat_ident == builtin.ident()
267                    && PatTy::matches_generics(ctx, generics, args)
268            }
269            (PatElem::Impl(_pat), PathElem::Impl(ImplElem::Ty(..))) => {
270                // TODO
271                false
272            }
273            (PatElem::Impl(pat), PathElem::Impl(ImplElem::Trait(impl_id))) => {
274                let Some(timpl) = ctx.trait_impls.get(*impl_id) else {
275                    return false;
276                };
277                let trait_name = ctx.item_name(timpl.impl_trait.id);
278                pat.matches_with_generics(ctx, trait_name, Some(&timpl.impl_trait.generics))
279            }
280            _ => false,
281        }
282    }
283}
284
285impl PatTy {
286    pub fn matches_generics(
287        ctx: &TranslatedCrate,
288        pats: &[Self],
289        generics: Option<&GenericArgs>,
290    ) -> bool {
291        let Some(generics) = generics else {
292            // If we'r ematching on a plain name without generics info, we ignore pattern generics.
293            return true;
294        };
295        if pats.is_empty() {
296            // If no generics are provided, this counts as a match.
297            return true;
298        }
299        // We don't include regions in patterns.
300        if pats.len() != generics.types.len() + generics.const_generics.len() {
301            return false;
302        }
303        let (type_pats, const_pats) = pats.split_at(generics.types.len());
304        let types_match = generics
305            .types
306            .iter()
307            .zip(type_pats)
308            .all(|(ty, pat)| pat.matches_ty(ctx, ty));
309        let consts_match = generics
310            .const_generics
311            .iter()
312            .zip(const_pats)
313            .all(|(c, pat)| pat.matches_const(ctx, c));
314        types_match && consts_match
315    }
316
317    pub fn matches_ty(&self, ctx: &TranslatedCrate, ty: &Ty) -> bool {
318        match (self, ty.kind()) {
319            (PatTy::Pat(p), _) => p.matches_ty(ctx, ty),
320            (PatTy::Ref(pat_mtbl, p_ty), TyKind::Ref(_, ty, ty_mtbl)) => {
321                pat_mtbl == ty_mtbl && p_ty.matches_ty(ctx, ty)
322            }
323            _ => false,
324        }
325    }
326
327    pub fn matches_const(&self, ctx: &TranslatedCrate, c: &ConstantExpr) -> bool {
328        match self {
329            PatTy::Pat(p) => p.matches_const(ctx, c),
330            PatTy::Ref(..) => false,
331        }
332    }
333}
334
335#[test]
336fn test_compare() {
337    use Ordering::*;
338    let tests = [
339        ("_", Less, "crate"),
340        ("crate::_", Less, "crate::foo"),
341        ("crate::foo", Less, "crate::foo::_"),
342    ];
343    for (x, o, y) in tests {
344        let x = Pattern::parse(x).unwrap();
345        let y = Pattern::parse(y).unwrap();
346        assert_eq!(x.compare(&y), o);
347    }
348}