Skip to main content

syn_sem/semantic/logic/
find_method.rs

1use crate::{
2    err,
3    etc::util::IntoPathSegments,
4    semantic::{
5        entry::GlobalCx,
6        logic::{term, util::var_name, ImplLogic},
7    },
8    ExprIn, TermIn, TriResult,
9};
10use logic_eval::{Expr, Name, Term, VAR_PREFIX};
11
12pub(crate) trait Host<'gcx> {
13    fn is_visible(&mut self, parent_path: &str, fn_ident: &str) -> TriResult<bool, ()>;
14}
15
16#[macro_export]
17macro_rules! impl_empty_method_host {
18    ($ty:ty) => {
19        impl<'gcx> $crate::semantic::logic::find_method::Host<'gcx> for $ty {
20            fn is_visible(
21                &mut self,
22                _parent_path: &str,
23                _fn_ident: &str,
24            ) -> $crate::TriResult<bool, ()> {
25                Ok(true)
26            }
27        }
28    };
29}
30
31pub(crate) struct MethodFinder<'a, 'gcx, H> {
32    gcx: &'gcx GlobalCx<'gcx>,
33    impl_logic: &'a mut ImplLogic<'gcx>,
34    host: &'a mut H,
35}
36
37impl<'a, 'gcx, H: Host<'gcx>> MethodFinder<'a, 'gcx, H>
38where
39    'gcx: 'a,
40    H: 'a,
41{
42    const CONCRETE_INTS: [&'static str; 12] = [
43        "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
44    ];
45    const CONCRETE_FLOATS: [&'static str; 2] = ["f32", "f64"];
46
47    pub(crate) fn new(
48        gcx: &'gcx GlobalCx<'gcx>,
49        impl_logic: &'a mut ImplLogic<'gcx>,
50        host: &'a mut H,
51    ) -> Self {
52        Self {
53            gcx,
54            impl_logic,
55            host,
56        }
57    }
58
59    /// Finds matching method signature, then puts it in the given `args`.
60    ///
61    /// * parent_path - The path of a trait or type
62    /// * fn_ident - Function name
63    /// * args - Actual types of values that are being passed to the function.
64    pub(crate) fn find_method_sig(
65        &mut self,
66        parent_path: &str,
67        fn_ident: &str,
68        args: &mut [TermIn<'gcx>],
69    ) -> TriResult<(), ()> {
70        const PARENT_VAR: &str = "$P";
71        const FN_NAME_VAR: &str = "$F";
72        const _: () = assert!(VAR_PREFIX == '$');
73
74        // Parent(trait or type) term
75        let parent_var = Term {
76            functor: Name::with_intern(PARENT_VAR, self.gcx),
77            args: [].into(),
78        };
79        let n = parent_path.segments().count();
80        let parent = match n as u32 {
81            0 => unreachable!(),
82            1 => Term {
83                functor: Name::with_intern(parent_path, self.gcx),
84                args: [parent_var].into(),
85            },
86            2.. => {
87                let empty_arg = || term::arg_n([].into(), self.gcx);
88                let mut segments: Vec<TermIn<'gcx>> = parent_path
89                    .segments()
90                    .map(|segment| Term {
91                        functor: Name::with_intern(segment, self.gcx),
92                        args: [empty_arg()].into(),
93                    })
94                    .collect();
95                segments[n - 1].args = [parent_var].into();
96                term::list_n(segments, self.gcx)
97            }
98        };
99
100        // Function name term
101        let fn_name_var = Term {
102            functor: Name::with_intern(FN_NAME_VAR, self.gcx),
103            args: [].into(),
104        };
105        let fn_name = Term {
106            functor: Name::with_intern(fn_ident, self.gcx),
107            args: [fn_name_var].into(),
108        };
109
110        // Signature term with argument variables. By replacing input arguments with variables
111        // except the receiver, we can find all methods which require type coercions.
112        let num_args = args.len();
113        let var_args: Vec<TermIn<'gcx>> = (0..num_args)
114            .map(|i| {
115                // Receiver
116                if i == 1 {
117                    args[1].clone()
118                } else {
119                    Term {
120                        functor: var_name(&i, self.gcx),
121                        args: [].into(),
122                    }
123                }
124            })
125            .collect();
126        let mut var_sig = term::sig_n(var_args, self.gcx);
127
128        if !self.host.is_visible(parent_path, fn_ident)? {
129            return err!(hard, "{parent_path}::{fn_ident} must be visible");
130        }
131
132        enum ReceiverState {
133            T,       // T
134            RefT,    // &T
135            RefMutT, // &mut T
136        }
137
138        let mut recv_state = ReceiverState::T;
139
140        loop {
141            if self.resolve(parent.clone(), fn_name.clone(), var_sig.clone(), args) {
142                break;
143            }
144
145            // We concern receiver(self) type only because it's a Rust rule. If other arguments are
146            // not compatible with the function we found, then it is a compile error. In other
147            // words, a method is determined by only the receiver, not by other parameters.
148            // ref: https://doc.rust-lang.org/reference/expressions/method-call-expr.html
149
150            match recv_state {
151                // Automatical borrow: T -> ref(T)
152                ReceiverState::T => {
153                    let receiver = &mut var_sig.args[1];
154                    *receiver = term::ref_1(receiver.clone(), self.gcx);
155                    recv_state = ReceiverState::RefT;
156                }
157                // Automatical borrow: ref(T) -> ref(mut(T))
158                ReceiverState::RefT => {
159                    let receiver = &mut var_sig.args[1]; // ref(T)
160                    let t = &mut receiver.args[0]; // T
161                    *t = term::mut_1(t.clone(), self.gcx); // receiver = ref(mut(T))
162                    recv_state = ReceiverState::RefMutT;
163                }
164
165                // Automatical dereference: Not yet implemented
166                //
167                // # Note
168                // Dereferencing here means `Deref` trait. not dereferencing to references. Result
169                // of dereferencing a reference is a value, not a type. See examples below.
170                // e.g. `Add` trait is implemented for `i32`, but dereferencing to a `&mut i32`
171                // cannot reach the implementation.
172                // - `Add<i32> for &i32` is explicitly implemented.
173                //   `let i: i32 = &0_i32 + 1_i32` // Ok
174                // - `Add<i32> for &mut i32` doesn't exist.
175                //   `let i: i32 = &mut 0_i32 + 1_i32` // Compile error
176                ReceiverState::RefMutT => {
177                    return err!(
178                        hard,
179                        "could not find a method for {parent_path}::{fn_ident}"
180                    );
181                }
182            }
183        }
184        Ok(())
185    }
186
187    /// Tries to resolve the given arguments. If it succeeded, returns true.
188    ///
189    /// * var_sig - Signature term. e.g. sig($0, X, $1, $2)
190    /// * args - Arguments that need to be resolved.
191    ///
192    /// # Note
193    ///
194    /// Every argument can have either only one varialble or zero. If not, it's undefined behavior.
195    fn resolve(
196        &mut self,
197        parent: TermIn<'gcx>,
198        fn_name: TermIn<'gcx>,
199        var_sig: TermIn<'gcx>,
200        args: &mut [TermIn<'gcx>],
201    ) -> bool {
202        // Looks for matching inherent method. If found, replaces the given arguments.
203        let inher_fn = term::inher_fn_3(parent.clone(), fn_name.clone(), var_sig.clone(), self.gcx);
204        if Self::_resolve(self.impl_logic, Expr::Term(inher_fn), args, self.gcx) {
205            return true;
206        }
207
208        // Looks for matching trait method. If found, replaces the given arguments.
209        let assoc_fn = term::assoc_fn_3(parent, fn_name, var_sig.clone(), self.gcx);
210        Self::_resolve(self.impl_logic, Expr::Term(assoc_fn), args, self.gcx)
211    }
212
213    /// Tries to resolve the given arguments. If it succeeded, returns true.
214    ///
215    /// # Note
216    ///
217    /// Every argument can have either only one varialble or zero. If not, it's undefined behavior.
218    fn _resolve(
219        impl_logic: &mut ImplLogic<'gcx>,
220        query: ExprIn<'gcx>,
221        args: &mut [TermIn<'gcx>],
222        gcx: &'gcx GlobalCx<'gcx>,
223    ) -> bool {
224        // Stores (nth of argument, corresponding term to the variable in the argument)
225        let mut candidates = Vec::new();
226
227        let mut cx = impl_logic.query(query);
228        while let Some(eval) = cx.prove_next() {
229            let old_num_candidates = candidates.len();
230
231            for assign in eval {
232                // Variables for function signature look like `$0`, `$1`, `$2`, and `$3` for
233                // example. They each correspond to output, receiver, 1st input, and 2nd input.
234                let Ok(nth) = assign.get_lhs_variable()[1..].parse::<usize>() else {
235                    continue;
236                };
237
238                // If the given argument is not matching, then discards this assignment. But
239                // receiver is an exception. It could be borrowed automatically.
240                let mut rhs = assign.rhs();
241                if nth != 1 && !Self::is_effective_same(&args[nth], &rhs) {
242                    candidates.truncate(old_num_candidates);
243                    break;
244                }
245
246                // We're going to replace variables only in the given arguments. So let's find out
247                // corresponding term to the variable.
248                if nth != 1 {
249                    if let Some(dst) =
250                        Self::find_corresponding_to_var_with_coercion(&args[nth], &rhs)
251                    {
252                        rhs = dst.clone();
253                    }
254                }
255
256                let pair = (nth, rhs);
257                if !candidates.contains(&pair) {
258                    candidates.push(pair);
259                }
260            }
261        }
262
263        if candidates.is_empty() {
264            return false;
265        }
266
267        while let Some((nth, mut rhs)) = candidates.pop() {
268            let mut ambiguous_int = false;
269            let mut ambiguous_float = false;
270
271            for j in (0..candidates.len()).rev() {
272                if candidates[j].0 == nth {
273                    if Self::is_similar_int(&rhs, &candidates[j].1) {
274                        ambiguous_int = true;
275                    } else if Self::is_similar_float(&rhs, &candidates[j].1) {
276                        ambiguous_float = true;
277                    } else {
278                        panic!("something went wrong: {}, {}", rhs, candidates[j].1);
279                    }
280                    candidates.swap_remove(j);
281                }
282            }
283
284            assert!(!(ambiguous_int && ambiguous_float));
285
286            if ambiguous_int {
287                Self::replace_concrete_int(&mut rhs, nth, gcx);
288            } else if ambiguous_float {
289                Self::replace_concrete_float(&mut rhs, nth, gcx);
290            }
291
292            debug_assert!(Self::num_containing_vars(&args[nth]) <= 1);
293
294            args[nth].replace_all(&mut |term| {
295                if term.functor.starts_with(VAR_PREFIX)
296                    && term.functor[1..].parse::<usize>() == Ok(nth)
297                {
298                    Some(rhs.clone())
299                } else {
300                    None
301                }
302            });
303        }
304        true
305    }
306
307    /// Finds a term in the `r` that corresponds to the first variable in the `l`. While searching,
308    /// `l` can be coerced to other types for further searching.
309    /// - e.g.
310    ///   l: $X, r: a                -> no coercion     -> return Some(a)
311    ///   l: ref(mut($X)), r: ref(a) -> ref($X), ref(a) -> return Some(a)
312    ///
313    /// # Note
314    ///
315    /// Undefined behavior if `r` contains variables.
316    fn find_corresponding_to_var_with_coercion<'r>(
317        l: &TermIn<'gcx>,
318        r: &'r TermIn<'gcx>,
319    ) -> Option<&'r TermIn<'gcx>> {
320        if let Some(matching_term) = Self::find_var_corresponding(l, r) {
321            return Some(matching_term);
322        }
323
324        // Tries various coercions.
325        // ref: https://doc.rust-lang.org/reference/type-coercions.html
326
327        if l.functor.as_ref() == term::FUNCTOR_REF {
328            if l.args[0].functor.as_ref() == term::FUNCTOR_MUT {
329                // &mut T -> &T
330                let mut cloned = l.clone(); // ref(mut(..))
331                let mut_ = &cloned.args[0]; // mut(..)
332                cloned.args[0] = mut_.args[0].clone(); // ref(..) := ref(mut(..))
333                Self::find_corresponding_to_var_with_coercion(&cloned, r)
334            } else {
335                // Not implemented yet
336                None
337            }
338        } else {
339            // Not implemented yet
340            None
341        }
342    }
343
344    /// Finds a term in the `r` that corresponds to the first variable in the `l`.
345    /// - e.g. a($X, $Y), a(b, c) -> return Some(b)
346    ///
347    /// # Note
348    ///
349    /// Undefined behavior if `r` contains variables.
350    fn find_var_corresponding<'r>(
351        l: &TermIn<'gcx>,
352        r: &'r TermIn<'gcx>,
353    ) -> Option<&'r TermIn<'gcx>> {
354        if !Self::is_effective_same(l, r) {
355            None
356        } else if l.is_variable() {
357            Some(r)
358        } else {
359            l.args
360                .iter()
361                .zip(&r.args)
362                .find_map(|(la, ra)| Self::find_var_corresponding(la, ra))
363        }
364    }
365
366    /// If two terms are exactly the same, returns true. If `l` contains variables in it and `r` is
367    /// the same with the `l` except vatiables, returns true.
368    /// - e.g. `a(b($X), $Y)` and `a(b(c), d)` are the same
369    ///
370    /// # Note
371    ///
372    /// Undefined behavior if `r` contains variables.
373    fn is_effective_same(l: &TermIn<'gcx>, r: &TermIn<'gcx>) -> bool {
374        if l.is_variable() {
375            return true;
376        }
377        l.functor == r.functor
378            && l.args.len() == r.args.len()
379            && l.args
380                .iter()
381                .zip(&r.args)
382                .all(|(la, ra)| Self::is_effective_same(la, ra))
383    }
384
385    /// Returns true if the two given terms are the same except concrete integer type like below.
386    ///
387    /// - l: ...int(i8)...
388    /// - r: ...int(i16)...
389    fn is_similar_int(l: &TermIn<'gcx>, r: &TermIn<'gcx>) -> bool {
390        if Self::CONCRETE_INTS.contains(&l.functor.as_ref())
391            && Self::CONCRETE_INTS.contains(&r.functor.as_ref())
392        {
393            return true;
394        }
395
396        l.functor == r.functor
397            && l.args.len() == r.args.len()
398            && l.args
399                .iter()
400                .zip(&r.args)
401                .any(|(la, ra)| Self::is_similar_int(la, ra))
402    }
403
404    /// Returns true if the two given terms are the same except concrete float type like below.
405    ///
406    /// - l: ...float(f32)...
407    /// - r: ...float(f64)...
408    fn is_similar_float(l: &TermIn<'gcx>, r: &TermIn<'gcx>) -> bool {
409        if Self::CONCRETE_FLOATS.contains(&l.functor.as_ref())
410            && Self::CONCRETE_FLOATS.contains(&r.functor.as_ref())
411        {
412            return true;
413        }
414
415        l.functor == r.functor
416            && l.args.len() == r.args.len()
417            && l.args
418                .iter()
419                .zip(&r.args)
420                .any(|(la, ra)| Self::is_similar_float(la, ra))
421    }
422
423    /// Replaces concrete int type from the given term with a variable.
424    ///
425    /// e.g. ...int(i32)... -> ...int($0)...
426    fn replace_concrete_int(term: &mut TermIn<'gcx>, var_ident: usize, gcx: &'gcx GlobalCx<'gcx>) {
427        if Self::CONCRETE_INTS.contains(&term.functor.as_ref()) {
428            term.functor = var_name(&var_ident, gcx);
429        }
430
431        for arg in &mut term.args {
432            Self::replace_concrete_int(arg, var_ident, gcx);
433        }
434    }
435
436    /// Replaces concrete int type from the given term with a variable.
437    ///
438    /// e.g. ...int(i32)... -> ...int($0)...
439    fn replace_concrete_float(
440        term: &mut TermIn<'gcx>,
441        var_ident: usize,
442        gcx: &'gcx GlobalCx<'gcx>,
443    ) {
444        if Self::CONCRETE_FLOATS.contains(&term.functor.as_ref()) {
445            term.functor = var_name(&var_ident, gcx);
446        }
447
448        for arg in &mut term.args {
449            Self::replace_concrete_float(arg, var_ident, gcx);
450        }
451    }
452
453    /// Returns number of variables in the given term.
454    fn num_containing_vars(term: &TermIn<'gcx>) -> usize {
455        if term.is_variable() {
456            return 1;
457        }
458        term.args
459            .iter()
460            .map(Self::num_containing_vars)
461            .sum::<usize>()
462    }
463}