Skip to main content

hir_ty/infer/
path.rs

1//! Path expression resolution.
2
3use hir_def::{
4    AdtId, AssocItemId, GenericDefId, ItemContainerId, Lookup,
5    expr_store::path::{Path, PathSegment},
6    hir::ExprOrPatIdPacked,
7    resolver::{ResolveValueResult, TypeNs, ValueNs},
8    signatures::{ConstSignature, FunctionSignature},
9};
10use hir_expand::name::Name;
11use rustc_type_ir::inherent::{SliceLike, Ty as _};
12use stdx::never;
13
14use crate::{
15    ExplicitDropMethodUseKind, InferenceDiagnostic, Span, ValueTyDefId,
16    infer::{
17        InferenceTyLoweringVarsCtx, diagnostics::InferenceTyLoweringContext as TyLoweringContext,
18    },
19    lower::{GenericPredicates, LifetimeElisionKind, LifetimeLoweringMode},
20    method_resolution::{self, CandidateId, MethodError},
21    next_solver::{
22        GenericArg, GenericArgs, TraitRef, Ty, Unnormalized, infer::traits::ObligationCause,
23        util::clauses_as_obligations,
24    },
25};
26
27use super::InferenceContext;
28
29impl<'db> InferenceContext<'db> {
30    pub(super) fn infer_path(
31        &mut self,
32        path: &Path,
33        id: ExprOrPatIdPacked,
34    ) -> Option<(ValueNs, Ty<'db>)> {
35        let (value, self_subst) = self.resolve_value_path_inner(path, id, false)?;
36
37        if let ValueNs::FunctionId(f) = value
38            && self.lang_items.Drop_drop.is_some_and(|drop_fn| drop_fn == f)
39        {
40            self.push_diagnostic(InferenceDiagnostic::ExplicitDropMethodUse {
41                kind: ExplicitDropMethodUseKind::Path(id),
42            });
43        }
44
45        let (value_def, generic_def, substs) =
46            match self.resolve_value_path(path, id, value, self_subst)? {
47                ValuePathResolution::GenericDef(value_def, generic_def, substs) => {
48                    (value_def, generic_def, substs)
49                }
50                ValuePathResolution::NonGeneric(ty) => return Some((value, ty)),
51            };
52        let args = self.insert_type_vars(substs);
53
54        self.add_required_obligations_for_value_path(id, generic_def, args);
55
56        let ty = self.db.value_ty(value_def)?.instantiate(self.interner(), args).skip_norm_wip();
57        let ty = self.process_remote_user_written_ty(ty);
58        Some((value, ty))
59    }
60
61    fn resolve_value_path(
62        &mut self,
63        path: &Path,
64        id: ExprOrPatIdPacked,
65        value: ValueNs,
66        self_subst: Option<GenericArgs<'db>>,
67    ) -> Option<ValuePathResolution<'db>> {
68        let value_def: ValueTyDefId = match value {
69            ValueNs::FunctionId(it) => it.into(),
70            ValueNs::ConstId(it) => it.into(),
71            ValueNs::StaticId(it) => it.into(),
72            ValueNs::StructId(it) => {
73                self.write_variant_resolution(id, it.into());
74
75                it.into()
76            }
77            ValueNs::EnumVariantId(it) => {
78                self.write_variant_resolution(id, it.into());
79
80                it.into()
81            }
82            ValueNs::LocalBinding(pat) => {
83                return match self.result.type_of_binding.get(pat) {
84                    Some(ty) => Some(ValuePathResolution::NonGeneric(ty.as_ref())),
85                    None => {
86                        never!("uninferred pattern?");
87                        None
88                    }
89                };
90            }
91            ValueNs::ImplSelf(impl_id) => {
92                let ty = self.db.impl_self_ty(impl_id).instantiate_identity().skip_norm_wip();
93                return if let Some((AdtId::StructId(struct_id), substs)) = ty.as_adt() {
94                    Some(ValuePathResolution::GenericDef(
95                        struct_id.into(),
96                        struct_id.into(),
97                        substs,
98                    ))
99                } else {
100                    // FIXME: report error, invalid Self reference
101                    None
102                };
103            }
104            ValueNs::GenericParam(it) => {
105                return Some(ValuePathResolution::NonGeneric(self.db.const_param_ty(it)));
106            }
107        };
108
109        let generic_def = value_def.to_generic_def_id(self.db);
110        if let GenericDefId::StaticId(_) = generic_def {
111            // `Static` is the kind of item that can never be generic currently. We can just skip the binders to get its type.
112            let ty = self.db.value_ty(value_def)?.skip_binder();
113            let ty = self.process_remote_user_written_ty(ty);
114            return Some(ValuePathResolution::NonGeneric(ty));
115        };
116
117        let substs = if self_subst.is_some_and(|it| !it.is_empty())
118            && matches!(value_def, ValueTyDefId::EnumVariantId(_))
119        {
120            // This is something like `TypeAlias::<Args>::EnumVariant`. Do not call `substs_from_path()`,
121            // as it'll try to re-lower the previous segment assuming it refers to the enum, but it refers
122            // to the type alias and they may have different generics.
123            self.types.empty.generic_args
124        } else {
125            self.with_ty_lowering(|ctx| {
126                let mut path_ctx = ctx.at_path(path, id);
127                let last_segment = path.segments().len().checked_sub(1);
128                if let Some(last_segment) = last_segment {
129                    path_ctx.set_current_segment(last_segment)
130                }
131                path_ctx.substs_from_path(value_def, true, false, id.into())
132            })
133        };
134
135        let parent_substs_len = self_subst.map_or(0, |it| it.len());
136        let substs = GenericArgs::fill_rest(
137            self.interner(),
138            generic_def.into(),
139            self_subst.iter().flat_map(|it| it.iter()).chain(substs.iter().skip(parent_substs_len)),
140            |_, id, _| GenericArg::error_from_id(self.interner(), id),
141        );
142
143        Some(ValuePathResolution::GenericDef(value_def, generic_def, substs))
144    }
145
146    pub(super) fn resolve_value_path_inner(
147        &mut self,
148        path: &Path,
149        id: ExprOrPatIdPacked,
150        no_diagnostics: bool,
151    ) -> Option<(ValueNs, Option<GenericArgs<'db>>)> {
152        // Don't use `self.make_ty()` here as we need `orig_ns`.
153        let mut vars_ctx = InferenceTyLoweringVarsCtx {
154            table: &mut self.table,
155            type_of_type_placeholder: &mut self.result.type_of_type_placeholder,
156        };
157        let mut ctx = TyLoweringContext::new(
158            self.db,
159            &self.resolver,
160            self.store,
161            &self.diagnostics,
162            self.store_owner,
163            self.generic_def,
164            &self.generics,
165            LifetimeElisionKind::Infer,
166            self.allow_using_generic_params,
167            &mut vars_ctx,
168            &self.defined_anon_consts,
169            LifetimeLoweringMode::LateParam,
170        );
171        let mut path_ctx = if no_diagnostics {
172            ctx.at_path_forget_diagnostics(path)
173        } else {
174            ctx.at_path(path, id)
175        };
176        let (value, self_subst) = if let Some(type_ref) = path.type_anchor() {
177            let last = path.segments().last()?;
178
179            let (ty, orig_ns) = path_ctx.ty_ctx().lower_ty_ext(type_ref);
180            let ty = path_ctx.expect_table().process_user_written_ty(ty);
181
182            path_ctx.ignore_last_segment();
183            let (ty, _) = path_ctx.lower_ty_relative_path(ty, orig_ns, true, id.into());
184            drop_ctx(ctx, no_diagnostics);
185            let ty = self.table.process_user_written_ty(ty);
186            self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))?
187        } else {
188            let hygiene = self.store.expr_or_pat_path_hygiene(id.unpack());
189            // FIXME: report error, unresolved first path segment
190            let value_or_partial = path_ctx.resolve_path_in_value_ns(hygiene)?;
191
192            match value_or_partial {
193                ResolveValueResult::ValueNs(it) => {
194                    drop_ctx(ctx, no_diagnostics);
195
196                    let args = if let Path::LangItem(..) = path {
197                        let def_and_container = match it {
198                            ValueNs::ConstId(it) => Some((it.into(), it.loc(self.db).container)),
199                            ValueNs::FunctionId(it) => Some((it.into(), it.loc(self.db).container)),
200                            _ => None,
201                        };
202                        let def_and_container =
203                            def_and_container.and_then(|(def, container)| match container {
204                                ItemContainerId::ImplId(it) => Some((def, it.into())),
205                                ItemContainerId::TraitId(it) => Some((def, it.into())),
206                                ItemContainerId::ExternBlockId(_)
207                                | ItemContainerId::ModuleId(_) => None,
208                            });
209                        def_and_container.map(|(def, container)| {
210                            let args = self.infcx().fresh_args_for_item(id.into(), container);
211                            self.write_assoc_resolution(id, def, args);
212                            args
213                        })
214                    } else {
215                        None
216                    };
217
218                    (it, args)
219                }
220                ResolveValueResult::Partial(def, remaining_index) => {
221                    // there may be more intermediate segments between the resolved one and
222                    // the end. Only the last segment needs to be resolved to a value; from
223                    // the segments before that, we need to get either a type or a trait ref.
224
225                    let remaining_segments = path.segments().skip(remaining_index);
226                    let is_before_last = remaining_segments.len() == 1;
227                    let last_segment = remaining_segments
228                        .last()
229                        .expect("there should be at least one segment here");
230
231                    let (resolution, substs) = match (def, is_before_last) {
232                        (TypeNs::TraitId(trait_), true) => {
233                            let self_ty = path_ctx.expect_table().next_ty_var(id.into());
234                            let trait_ref = path_ctx.lower_trait_ref_from_resolved_path(
235                                trait_,
236                                self_ty,
237                                true,
238                                id.into(),
239                            );
240                            drop_ctx(ctx, no_diagnostics);
241                            self.resolve_trait_assoc_item(trait_ref, last_segment, id)
242                        }
243                        (def, _) => {
244                            // Either we already have a type (e.g. `Vec::new`), or we have a
245                            // trait but it's not the last segment, so the next segment
246                            // should resolve to an associated type of that trait (e.g. `<T
247                            // as Iterator>::Item::default`)
248                            path_ctx.ignore_last_segment();
249                            let (ty, _) = path_ctx.lower_partly_resolved_path(def, true, id.into());
250                            drop_ctx(ctx, no_diagnostics);
251                            if ty.is_ty_error() {
252                                return None;
253                            }
254
255                            let ty = self.process_user_written_ty(ty);
256
257                            self.resolve_ty_assoc_item(ty, last_segment.name, id)
258                        }
259                    }?;
260                    (resolution, Some(substs))
261                }
262            }
263        };
264        return Some((value, self_subst));
265
266        #[inline]
267        fn drop_ctx(mut ctx: TyLoweringContext<'_, '_>, no_diagnostics: bool) {
268            if no_diagnostics {
269                ctx.forget_diagnostics();
270            }
271        }
272    }
273
274    pub(super) fn add_required_obligations_for_value_path(
275        &mut self,
276        node: ExprOrPatIdPacked,
277        def: GenericDefId,
278        subst: GenericArgs<'db>,
279    ) {
280        let interner = self.interner();
281        let predicates = GenericPredicates::query_all(self.db, def);
282        let param_env = self.table.param_env;
283        self.table.register_predicates(clauses_as_obligations(
284            predicates
285                .iter_instantiated(interner, subst.as_slice())
286                .map(Unnormalized::skip_norm_wip),
287            ObligationCause::new(node),
288            param_env,
289        ));
290    }
291
292    fn resolve_trait_assoc_item(
293        &mut self,
294        trait_ref: TraitRef<'db>,
295        segment: PathSegment<'_>,
296        id: ExprOrPatIdPacked,
297    ) -> Option<(ValueNs, GenericArgs<'db>)> {
298        let trait_ = trait_ref.def_id.0;
299        let item =
300            trait_.trait_items(self.db).items.iter().map(|(_name, id)| *id).find_map(|item| {
301                match item {
302                    AssocItemId::FunctionId(func) => {
303                        if segment.name == &FunctionSignature::of(self.db, func).name {
304                            Some(CandidateId::FunctionId(func))
305                        } else {
306                            None
307                        }
308                    }
309
310                    AssocItemId::ConstId(konst) => {
311                        if ConstSignature::of(self.db, konst).name.as_ref() == Some(segment.name) {
312                            Some(CandidateId::ConstId(konst))
313                        } else {
314                            None
315                        }
316                    }
317                    AssocItemId::TypeAliasId(_) => None,
318                }
319            })?;
320        let def = match item {
321            CandidateId::FunctionId(f) => ValueNs::FunctionId(f),
322            CandidateId::ConstId(c) => ValueNs::ConstId(c),
323        };
324
325        self.write_assoc_resolution(id, item, trait_ref.args);
326        Some((def, trait_ref.args))
327    }
328
329    fn resolve_ty_assoc_item(
330        &mut self,
331        ty: Ty<'db>,
332        name: &Name,
333        id: ExprOrPatIdPacked,
334    ) -> Option<(ValueNs, GenericArgs<'db>)> {
335        if ty.is_ty_error() {
336            return None;
337        }
338
339        if let Some(result) = self.resolve_enum_variant_on_ty(ty, name, id) {
340            return Some(result);
341        }
342
343        let res = self.with_method_resolution(Span::Dummy, Span::Dummy, |ctx| {
344            ctx.probe_for_name(method_resolution::Mode::Path, name.clone(), ty)
345        });
346        let (item, visible) = match res {
347            Ok(res) => (res.item, true),
348            Err(error) => match error {
349                MethodError::PrivateMatch(candidate_id) => (candidate_id.item, false),
350                _ => {
351                    self.push_diagnostic(InferenceDiagnostic::UnresolvedAssocItem { id });
352                    return None;
353                }
354            },
355        };
356
357        let (def, container) = match item {
358            CandidateId::FunctionId(f) => (ValueNs::FunctionId(f), f.lookup(self.db).container),
359            CandidateId::ConstId(c) => (ValueNs::ConstId(c), c.lookup(self.db).container),
360        };
361        let substs = match container {
362            ItemContainerId::ImplId(impl_id) => {
363                let impl_substs = self.table.fresh_args_for_item(id.into(), impl_id.into());
364                let impl_self_ty = self
365                    .db
366                    .impl_self_ty(impl_id)
367                    .instantiate(self.interner(), impl_substs)
368                    .skip_norm_wip();
369                _ = self.demand_eqtype(id, impl_self_ty, ty);
370                impl_substs
371            }
372            ItemContainerId::TraitId(trait_) => {
373                // we're picking this method
374                GenericArgs::fill_rest(
375                    self.interner(),
376                    trait_.into(),
377                    [ty.into()],
378                    |_, param, _| self.table.var_for_def(param, id.into()),
379                )
380            }
381            ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => {
382                never!("assoc item contained in module/extern block");
383                return None;
384            }
385        };
386
387        self.write_assoc_resolution(id, item, substs);
388        if !visible {
389            let item = match item {
390                CandidateId::FunctionId(it) => it.into(),
391                CandidateId::ConstId(it) => it.into(),
392            };
393            self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id, item });
394        }
395        Some((def, substs))
396    }
397
398    fn resolve_enum_variant_on_ty(
399        &mut self,
400        ty: Ty<'db>,
401        name: &Name,
402        id: ExprOrPatIdPacked,
403    ) -> Option<(ValueNs, GenericArgs<'db>)> {
404        let ty = self.table.try_structurally_resolve_type(id.into(), ty);
405        let (enum_id, subst) = match ty.as_adt() {
406            Some((AdtId::EnumId(e), subst)) => (e, subst),
407            _ => return None,
408        };
409        let enum_data = enum_id.enum_variants(self.db);
410        let variant = enum_data.variant(name)?;
411        self.write_variant_resolution(id, variant.into());
412        Some((ValueNs::EnumVariantId(variant), subst))
413    }
414}
415
416#[derive(Debug)]
417enum ValuePathResolution<'db> {
418    // It's awkward to wrap a single ID in two enums, but we need both and this saves fallible
419    // conversion between them + `unwrap()`.
420    GenericDef(ValueTyDefId, GenericDefId, GenericArgs<'db>),
421    NonGeneric(Ty<'db>),
422}