spade_typeinference/
method_resolution.rs

1use itertools::Itertools;
2use spade_common::location_info::{Loc, WithLocation};
3use spade_common::name::{Identifier, NameID};
4
5use spade_diagnostics::Diagnostic;
6use spade_hir::{ImplTarget, TypeExpression, TypeSpec};
7use spade_types::KnownType;
8
9use crate::equation::TypeVar;
10use crate::traits::{TraitImpl, TraitImplList};
11
12#[derive(Debug, PartialEq, Eq)]
13pub enum FunctionLikeName {
14    Method(Identifier),
15    Free(NameID),
16}
17impl std::fmt::Display for FunctionLikeName {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        match self {
20            FunctionLikeName::Method(n) => write!(f, "{n}"),
21            FunctionLikeName::Free(n) => write!(f, "{n}"),
22        }
23    }
24}
25
26pub trait IntoImplTarget {
27    fn into_impl_target(&self) -> Option<ImplTarget>;
28}
29
30impl IntoImplTarget for KnownType {
31    fn into_impl_target(&self) -> Option<ImplTarget> {
32        match self {
33            KnownType::Named(name) => Some(ImplTarget::Named(name.clone())),
34            KnownType::Integer(_) | KnownType::Bool(_) => None,
35            KnownType::Tuple => None,
36            KnownType::Array => Some(ImplTarget::Array),
37            KnownType::Wire => Some(ImplTarget::Wire),
38            KnownType::Inverted => Some(ImplTarget::Inverted),
39        }
40    }
41}
42
43/// Attempts to look up which function to call when calling `method` on a var
44/// of type `self_type`.
45/// Returns the method to call if it is fully known and exists, an error if there is
46/// no such method, or None if the method is ambiguous
47pub fn select_method(
48    expr: Loc<()>,
49    self_type: &TypeVar,
50    method: &Loc<Identifier>,
51    trait_impls: &TraitImplList,
52) -> Result<Option<Loc<NameID>>, Diagnostic> {
53    let target = self_type
54        .expect_known::<_, _, _, Option<ImplTarget>>(
55            |ktype, _params| ktype.into_impl_target(),
56            || None,
57        )
58        .ok_or_else(|| Diagnostic::error(expr, format!("{self_type} does not have any methods")))?;
59
60    // Go to the item list to check if this name has any methods
61    let impls = trait_impls.inner.get(&target).cloned().unwrap_or(vec![]);
62
63    // Gather all the candidate methods which we may want to call.
64    let (matched_candidates, maybe_candidates, unmatched_candidates): (Vec<_>, Vec<_>, Vec<_>) =
65        impls
66            .iter()
67            .flat_map(
68                |TraitImpl {
69                     name: _,
70                     type_params: _,
71                     impl_block: r#impl,
72                 }| {
73                    r#impl.fns.iter().map(move |(fn_name, actual_fn)| {
74                        if fn_name == &method.inner {
75                            let is_overlapping = spec_is_overlapping(&r#impl.target, self_type);
76                            let selected = actual_fn.0.clone().at_loc(&actual_fn.1);
77                            match is_overlapping {
78                                Overlap::Yes => (Some(selected), None, None),
79                                Overlap::Maybe => (None, Some(selected), None),
80                                Overlap::No => (None, None, Some(&r#impl.target)),
81                            }
82                        } else {
83                            (None, None, None)
84                        }
85                    })
86                },
87            )
88            .multiunzip();
89
90    let matched_candidates = matched_candidates
91        .into_iter()
92        .filter_map(|x| x)
93        .collect::<Vec<_>>();
94    let maybe_candidates = maybe_candidates
95        .into_iter()
96        .filter_map(|x| x)
97        .collect::<Vec<_>>();
98    let unmatched_candidates = unmatched_candidates
99        .into_iter()
100        .filter_map(|x| x)
101        .collect::<Vec<_>>();
102
103    if !maybe_candidates.is_empty() {
104        return Ok(None);
105    }
106
107    let final_method = match matched_candidates.as_slice() {
108        [name] => name,
109        [] => {
110            let mut d =
111                Diagnostic::error(method, format!("`{self_type}` has no method `{method}`"))
112                    .primary_label("No such method")
113                    .secondary_label(expr, format!("This has type `{self_type}`"));
114
115            match unmatched_candidates.as_slice() {
116                [] => {}
117                [one] => {
118                    d.add_help(format!("The method exists for `{one}`"));
119                }
120                multiple => {
121                    d.add_help(format!(
122                        "The method exists for `{}`, and `{}`",
123                        multiple[0..multiple.len() - 1]
124                            .iter()
125                            .map(|t| format!("`{t}`"))
126                            .join(", "),
127                        multiple.last().unwrap()
128                    ));
129                }
130            };
131            return Err(d);
132        }
133        _ => {
134            return Err(Diagnostic::bug(
135                method,
136                "Multiple candidates satisfy this method",
137            ))
138        }
139    };
140
141    Ok(Some(final_method.clone()))
142}
143
144enum Overlap {
145    /// We know for sure if there is overlap
146    Yes,
147    No,
148    /// We Are not sure yet if there is overlap. This happens if we have X<_>
149    /// satisfies but X<T> where T is concrete
150    Maybe,
151}
152
153trait IterExt {
154    fn all_overlap(self) -> Overlap;
155}
156
157impl<T> IterExt for T
158where
159    T: Iterator<Item = Overlap>,
160{
161    fn all_overlap(self) -> Overlap {
162        for o in self {
163            match o {
164                Overlap::Yes => {}
165                Overlap::No => return Overlap::No,
166                Overlap::Maybe => return Overlap::Maybe,
167            }
168        }
169        Overlap::Yes
170    }
171}
172
173fn specs_are_overlapping(specs: &[Loc<TypeSpec>], vars: &[TypeVar]) -> Overlap {
174    if specs.len() == vars.len() {
175        specs
176            .iter()
177            .zip(vars)
178            .map(|(s, v)| spec_is_overlapping(s, v))
179            .all_overlap()
180    } else {
181        Overlap::No
182    }
183}
184
185fn expr_is_overlapping(expr: &TypeExpression, var: &TypeVar) -> Overlap {
186    match (&expr, var) {
187        (TypeExpression::Integer(eval), TypeVar::Known(_, KnownType::Integer(vval), _)) => {
188            if eval == vval {
189                Overlap::Yes
190            } else {
191                Overlap::No
192            }
193        }
194        (TypeExpression::Integer(_), TypeVar::Unknown(_, _, _, _)) => Overlap::Maybe,
195        (TypeExpression::Integer(_), _) => {
196            unreachable!("Non integer and non-generic type matched with integer")
197        }
198        (TypeExpression::TypeSpec(s), v) => spec_is_overlapping(s, v),
199        (TypeExpression::ConstGeneric(_), _) => {
200            unreachable!("Const generic in expr_is_overlapping")
201        }
202    }
203}
204
205fn exprs_are_overlapping(exprs: &[Loc<TypeExpression>], vars: &[TypeVar]) -> Overlap {
206    if exprs.len() == vars.len() {
207        exprs
208            .iter()
209            .zip(vars)
210            .map(|(e, v)| expr_is_overlapping(e, v))
211            .all_overlap()
212    } else {
213        Overlap::No
214    }
215}
216
217fn spec_is_overlapping(spec: &TypeSpec, var: &TypeVar) -> Overlap {
218    match (spec, var) {
219        // Generics overlap with anything
220        (TypeSpec::Generic(_), _) => Overlap::Yes,
221        // For anything non-generic, we need a concrete type to know if there is overlap.
222        (_, TypeVar::Unknown(_, _, _, _)) => Overlap::Maybe,
223        (
224            TypeSpec::Declared(specname, specparams),
225            TypeVar::Known(_, KnownType::Named(varname), varparams),
226        ) => {
227            if &specname.inner == varname {
228                exprs_are_overlapping(specparams, varparams)
229            } else {
230                Overlap::No
231            }
232        }
233        (TypeSpec::Declared(_, _), _) => Overlap::No,
234
235        // NOTE: This block currently cannot be tested because we can't add methods to
236        // anything other than declared types
237        (TypeSpec::Tuple(sspecs), TypeVar::Known(_, KnownType::Tuple, vvars)) => {
238            specs_are_overlapping(sspecs, vvars)
239        }
240        (TypeSpec::Tuple(_), _) => Overlap::No,
241        (TypeSpec::Array { inner, size }, TypeVar::Known(_, KnownType::Array, vvars)) => [
242            spec_is_overlapping(inner, &vvars[0]),
243            expr_is_overlapping(size, &vvars[1]),
244        ]
245        .into_iter()
246        .all_overlap(),
247        (TypeSpec::Array { .. }, _) => Overlap::No,
248        (TypeSpec::Inverted(sinner), TypeVar::Known(_, KnownType::Inverted, vinner))
249        | (TypeSpec::Wire(sinner), TypeVar::Known(_, KnownType::Wire, vinner)) => {
250            spec_is_overlapping(sinner, &vinner[0])
251        }
252        (TypeSpec::Inverted(_), _) => Overlap::No,
253        (TypeSpec::Wire(_), _) => Overlap::No,
254
255        // TraitSelf cannot appear as the impl target, so what we do here is irrelevant
256        (TypeSpec::TraitSelf(_), TypeVar::Known(_, _, _)) => {
257            unreachable!("Trait self in impl target")
258        }
259        (TypeSpec::Wildcard(_), _) => unreachable!("Wildcard during type spec overlap check"),
260    }
261}