hir_ty/
autoderef.rs

1//! In certain situations, rust automatically inserts derefs as necessary: for
2//! example, field accesses `foo.bar` still work when `foo` is actually a
3//! reference to a type with the field `bar`. This is an approximation of the
4//! logic in rustc (which lives in rustc_hir_analysis/check/autoderef.rs).
5
6use std::mem;
7
8use chalk_ir::cast::Cast;
9use hir_def::lang_item::LangItem;
10use hir_expand::name::Name;
11use intern::sym;
12use triomphe::Arc;
13
14use crate::{
15    Canonical, Goal, Interner, ProjectionTyExt, TraitEnvironment, Ty, TyBuilder, TyKind,
16    db::HirDatabase, infer::unify::InferenceTable,
17};
18
19const AUTODEREF_RECURSION_LIMIT: usize = 20;
20
21#[derive(Debug)]
22pub(crate) enum AutoderefKind {
23    Builtin,
24    Overloaded,
25}
26
27/// Returns types that `ty` transitively dereferences to. This function is only meant to be used
28/// outside `hir-ty`.
29///
30/// It is guaranteed that:
31/// - the yielded types don't contain inference variables (but may contain `TyKind::Error`).
32/// - a type won't be yielded more than once; in other words, the returned iterator will stop if it
33///   detects a cycle in the deref chain.
34pub fn autoderef(
35    db: &dyn HirDatabase,
36    env: Arc<TraitEnvironment>,
37    ty: Canonical<Ty>,
38) -> impl Iterator<Item = Ty> {
39    let mut table = InferenceTable::new(db, env);
40    let ty = table.instantiate_canonical(ty);
41    let mut autoderef = Autoderef::new_no_tracking(&mut table, ty, false, false);
42    let mut v = Vec::new();
43    while let Some((ty, _steps)) = autoderef.next() {
44        // `ty` may contain unresolved inference variables. Since there's no chance they would be
45        // resolved, just replace with fallback type.
46        let resolved = autoderef.table.resolve_completely(ty);
47
48        // If the deref chain contains a cycle (e.g. `A` derefs to `B` and `B` derefs to `A`), we
49        // would revisit some already visited types. Stop here to avoid duplication.
50        //
51        // XXX: The recursion limit for `Autoderef` is currently 20, so `Vec::contains()` shouldn't
52        // be too expensive. Replace this duplicate check with `FxHashSet` if it proves to be more
53        // performant.
54        if v.contains(&resolved) {
55            break;
56        }
57        v.push(resolved);
58    }
59    v.into_iter()
60}
61
62trait TrackAutoderefSteps {
63    fn len(&self) -> usize;
64    fn push(&mut self, kind: AutoderefKind, ty: &Ty);
65}
66
67impl TrackAutoderefSteps for usize {
68    fn len(&self) -> usize {
69        *self
70    }
71    fn push(&mut self, _: AutoderefKind, _: &Ty) {
72        *self += 1;
73    }
74}
75impl TrackAutoderefSteps for Vec<(AutoderefKind, Ty)> {
76    fn len(&self) -> usize {
77        self.len()
78    }
79    fn push(&mut self, kind: AutoderefKind, ty: &Ty) {
80        self.push((kind, ty.clone()));
81    }
82}
83
84#[derive(Debug)]
85pub(crate) struct Autoderef<'table, 'db, T = Vec<(AutoderefKind, Ty)>> {
86    pub(crate) table: &'table mut InferenceTable<'db>,
87    ty: Ty,
88    at_start: bool,
89    steps: T,
90    explicit: bool,
91    use_receiver_trait: bool,
92}
93
94impl<'table, 'db> Autoderef<'table, 'db> {
95    pub(crate) fn new(
96        table: &'table mut InferenceTable<'db>,
97        ty: Ty,
98        explicit: bool,
99        use_receiver_trait: bool,
100    ) -> Self {
101        let ty = table.resolve_ty_shallow(&ty);
102        Autoderef { table, ty, at_start: true, steps: Vec::new(), explicit, use_receiver_trait }
103    }
104
105    pub(crate) fn steps(&self) -> &[(AutoderefKind, Ty)] {
106        &self.steps
107    }
108}
109
110impl<'table, 'db> Autoderef<'table, 'db, usize> {
111    pub(crate) fn new_no_tracking(
112        table: &'table mut InferenceTable<'db>,
113        ty: Ty,
114        explicit: bool,
115        use_receiver_trait: bool,
116    ) -> Self {
117        let ty = table.resolve_ty_shallow(&ty);
118        Autoderef { table, ty, at_start: true, steps: 0, explicit, use_receiver_trait }
119    }
120}
121
122#[allow(private_bounds)]
123impl<T: TrackAutoderefSteps> Autoderef<'_, '_, T> {
124    pub(crate) fn step_count(&self) -> usize {
125        self.steps.len()
126    }
127
128    pub(crate) fn final_ty(&self) -> Ty {
129        self.ty.clone()
130    }
131}
132
133impl<T: TrackAutoderefSteps> Iterator for Autoderef<'_, '_, T> {
134    type Item = (Ty, usize);
135
136    #[tracing::instrument(skip_all)]
137    fn next(&mut self) -> Option<Self::Item> {
138        if mem::take(&mut self.at_start) {
139            return Some((self.ty.clone(), 0));
140        }
141
142        if self.steps.len() > AUTODEREF_RECURSION_LIMIT {
143            return None;
144        }
145
146        let (kind, new_ty) =
147            autoderef_step(self.table, self.ty.clone(), self.explicit, self.use_receiver_trait)?;
148
149        self.steps.push(kind, &self.ty);
150        self.ty = new_ty;
151
152        Some((self.ty.clone(), self.step_count()))
153    }
154}
155
156pub(crate) fn autoderef_step(
157    table: &mut InferenceTable<'_>,
158    ty: Ty,
159    explicit: bool,
160    use_receiver_trait: bool,
161) -> Option<(AutoderefKind, Ty)> {
162    if let Some(derefed) = builtin_deref(table.db, &ty, explicit) {
163        Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed)))
164    } else {
165        Some((AutoderefKind::Overloaded, deref_by_trait(table, ty, use_receiver_trait)?))
166    }
167}
168
169pub(crate) fn builtin_deref<'ty>(
170    db: &dyn HirDatabase,
171    ty: &'ty Ty,
172    explicit: bool,
173) -> Option<&'ty Ty> {
174    match ty.kind(Interner) {
175        TyKind::Ref(.., ty) => Some(ty),
176        TyKind::Raw(.., ty) if explicit => Some(ty),
177        &TyKind::Adt(chalk_ir::AdtId(adt), ref substs) if crate::lang_items::is_box(db, adt) => {
178            substs.at(Interner, 0).ty(Interner)
179        }
180        _ => None,
181    }
182}
183
184pub(crate) fn deref_by_trait(
185    table @ &mut InferenceTable { db, .. }: &mut InferenceTable<'_>,
186    ty: Ty,
187    use_receiver_trait: bool,
188) -> Option<Ty> {
189    let _p = tracing::info_span!("deref_by_trait").entered();
190    if table.resolve_ty_shallow(&ty).inference_var(Interner).is_some() {
191        // don't try to deref unknown variables
192        return None;
193    }
194
195    let trait_id = || {
196        // FIXME: Remove the `false` once `Receiver` needs to be stabilized, doing so will
197        // effectively bump the MSRV of rust-analyzer to 1.84 due to 1.83 and below lacking the
198        // blanked impl on `Deref`.
199        #[expect(clippy::overly_complex_bool_expr)]
200        if use_receiver_trait
201            && false
202            && let Some(receiver) = LangItem::Receiver.resolve_trait(db, table.trait_env.krate)
203        {
204            return Some(receiver);
205        }
206        // Old rustc versions might not have `Receiver` trait.
207        // Fallback to `Deref` if they don't
208        LangItem::Deref.resolve_trait(db, table.trait_env.krate)
209    };
210    let trait_id = trait_id()?;
211    let target =
212        trait_id.trait_items(db).associated_type_by_name(&Name::new_symbol_root(sym::Target))?;
213
214    let projection = {
215        let b = TyBuilder::subst_for_def(db, trait_id, None);
216        if b.remaining() != 1 {
217            // the Target type + Deref trait should only have one generic parameter,
218            // namely Deref's Self type
219            return None;
220        }
221        let deref_subst = b.push(ty).build();
222        TyBuilder::assoc_type_projection(db, target, Some(deref_subst)).build()
223    };
224
225    // Check that the type implements Deref at all
226    let trait_ref = projection.trait_ref(db);
227    let implements_goal: Goal = trait_ref.cast(Interner);
228    if table.try_obligation(implements_goal.clone()).no_solution() {
229        return None;
230    }
231
232    table.register_obligation(implements_goal);
233
234    let result = table.normalize_projection_ty(projection);
235    Some(table.resolve_ty_shallow(&result))
236}