//! Path expression resolution. use hir_def::{ AdtId, AssocItemId, GenericDefId, ItemContainerId, Lookup, expr_store::path::{Path, PathSegment}, resolver::{ResolveValueResult, TypeNs, ValueNs}, }; use hir_expand::name::Name; use rustc_type_ir::inherent::{SliceLike, Ty as _}; use stdx::never; use crate::{ InferenceDiagnostic, ValueTyDefId, generics::generics, infer::diagnostics::InferenceTyLoweringContext as TyLoweringContext, lower::{GenericPredicates, LifetimeElisionKind}, method_resolution::{self, CandidateId, MethodError}, next_solver::{ GenericArg, GenericArgs, TraitRef, Ty, infer::traits::{Obligation, ObligationCause}, util::clauses_as_obligations, }, }; use super::{ExprOrPatId, InferenceContext, InferenceTyDiagnosticSource}; impl<'db> InferenceContext<'_, 'db> { pub(super) fn infer_path(&mut self, path: &Path, id: ExprOrPatId) -> Option> { let (value_def, generic_def, substs) = match self.resolve_value_path(path, id)? { ValuePathResolution::GenericDef(value_def, generic_def, substs) => { (value_def, generic_def, substs) } ValuePathResolution::NonGeneric(ty) => return Some(ty), }; let args = self.insert_type_vars(substs); self.add_required_obligations_for_value_path(generic_def, args); let ty = self.db.value_ty(value_def)?.instantiate(self.interner(), args); let ty = self.process_remote_user_written_ty(ty); Some(ty) } fn resolve_value_path( &mut self, path: &Path, id: ExprOrPatId, ) -> Option> { let (value, self_subst) = self.resolve_value_path_inner(path, id, false)?; let value_def: ValueTyDefId = match value { ValueNs::FunctionId(it) => it.into(), ValueNs::ConstId(it) => it.into(), ValueNs::StaticId(it) => it.into(), ValueNs::StructId(it) => { self.write_variant_resolution(id, it.into()); it.into() } ValueNs::EnumVariantId(it) => { self.write_variant_resolution(id, it.into()); it.into() } ValueNs::LocalBinding(pat) => { return match self.result.type_of_binding.get(pat) { Some(ty) => Some(ValuePathResolution::NonGeneric(ty.as_ref())), None => { never!("uninferred pattern?"); None } }; } ValueNs::ImplSelf(impl_id) => { let ty = self.db.impl_self_ty(impl_id).instantiate_identity(); return if let Some((AdtId::StructId(struct_id), substs)) = ty.as_adt() { Some(ValuePathResolution::GenericDef( struct_id.into(), struct_id.into(), substs, )) } else { // FIXME: report error, invalid Self reference None }; } ValueNs::GenericParam(it) => { return Some(ValuePathResolution::NonGeneric(self.db.const_param_ty_ns(it))); } }; let generic_def = value_def.to_generic_def_id(self.db); if let GenericDefId::StaticId(_) = generic_def { // `Static` is the kind of item that can never be generic currently. We can just skip the binders to get its type. let ty = self.db.value_ty(value_def)?.skip_binder(); let ty = self.process_remote_user_written_ty(ty); return Some(ValuePathResolution::NonGeneric(ty)); }; let substs = if self_subst.is_some_and(|it| !it.is_empty()) && matches!(value_def, ValueTyDefId::EnumVariantId(_)) { // This is something like `TypeAlias::::EnumVariant`. Do not call `substs_from_path()`, // as it'll try to re-lower the previous segment assuming it refers to the enum, but it refers // to the type alias and they may have different generics. self.types.empty.generic_args } else { self.with_body_ty_lowering(|ctx| { let mut path_ctx = ctx.at_path(path, id); let last_segment = path.segments().len().checked_sub(1); if let Some(last_segment) = last_segment { path_ctx.set_current_segment(last_segment) } path_ctx.substs_from_path(value_def, true, false) }) }; let parent_substs_len = self_subst.map_or(0, |it| it.len()); let substs = GenericArgs::fill_rest( self.interner(), generic_def.into(), self_subst.iter().flat_map(|it| it.iter()).chain(substs.iter().skip(parent_substs_len)), |_, id, _| GenericArg::error_from_id(self.interner(), id), ); Some(ValuePathResolution::GenericDef(value_def, generic_def, substs)) } pub(super) fn resolve_value_path_inner( &mut self, path: &Path, id: ExprOrPatId, no_diagnostics: bool, ) -> Option<(ValueNs, Option>)> { // Don't use `self.make_ty()` here as we need `orig_ns`. let mut ctx = TyLoweringContext::new( self.db, &self.resolver, self.body, &self.diagnostics, InferenceTyDiagnosticSource::Body, self.generic_def, LifetimeElisionKind::Infer, ); let mut path_ctx = if no_diagnostics { ctx.at_path_forget_diagnostics(path) } else { ctx.at_path(path, id) }; let (value, self_subst) = if let Some(type_ref) = path.type_anchor() { let last = path.segments().last()?; let (ty, orig_ns) = path_ctx.ty_ctx().lower_ty_ext(type_ref); let ty = self.table.process_user_written_ty(ty); path_ctx.ignore_last_segment(); let (ty, _) = path_ctx.lower_ty_relative_path(ty, orig_ns, true); drop_ctx(ctx, no_diagnostics); let ty = self.table.process_user_written_ty(ty); self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))? } else { let hygiene = self.body.expr_or_pat_path_hygiene(id); // FIXME: report error, unresolved first path segment let value_or_partial = path_ctx.resolve_path_in_value_ns(hygiene)?; match value_or_partial { ResolveValueResult::ValueNs(it) => { drop_ctx(ctx, no_diagnostics); (it, None) } ResolveValueResult::Partial(def, remaining_index) => { // there may be more intermediate segments between the resolved one and // the end. Only the last segment needs to be resolved to a value; from // the segments before that, we need to get either a type or a trait ref. let remaining_segments = path.segments().skip(remaining_index); let is_before_last = remaining_segments.len() == 1; let last_segment = remaining_segments .last() .expect("there should be at least one segment here"); let (resolution, substs) = match (def, is_before_last) { (TypeNs::TraitId(trait_), true) => { let self_ty = self.table.next_ty_var(); let trait_ref = path_ctx.lower_trait_ref_from_resolved_path(trait_, self_ty, true); drop_ctx(ctx, no_diagnostics); self.resolve_trait_assoc_item(trait_ref, last_segment, id) } (def, _) => { // Either we already have a type (e.g. `Vec::new`), or we have a // trait but it's not the last segment, so the next segment // should resolve to an associated type of that trait (e.g. `::Item::default`) path_ctx.ignore_last_segment(); let (ty, _) = path_ctx.lower_partly_resolved_path(def, true); drop_ctx(ctx, no_diagnostics); if ty.is_ty_error() { return None; } let ty = self.process_user_written_ty(ty); self.resolve_ty_assoc_item(ty, last_segment.name, id) } }?; (resolution, Some(substs)) } } }; return Some((value, self_subst)); #[inline] fn drop_ctx(mut ctx: TyLoweringContext<'_, '_>, no_diagnostics: bool) { if no_diagnostics { ctx.forget_diagnostics(); } } } fn add_required_obligations_for_value_path( &mut self, def: GenericDefId, subst: GenericArgs<'db>, ) { let interner = self.interner(); let predicates = GenericPredicates::query_all(self.db, def); let param_env = self.table.param_env; self.table.register_predicates(clauses_as_obligations( predicates.iter_instantiated_copied(interner, subst.as_slice()), ObligationCause::new(), param_env, )); // We need to add `Self: Trait` obligation when `def` is a trait assoc item. let container = match def { GenericDefId::FunctionId(id) => id.lookup(self.db).container, GenericDefId::ConstId(id) => id.lookup(self.db).container, _ => return, }; if let ItemContainerId::TraitId(trait_) = container { let parent_len = generics(self.db, def).parent_generics().map_or(0, |g| g.len_self()); let parent_subst = GenericArgs::new_from_slice(&subst.as_slice()[..parent_len]); let trait_ref = TraitRef::new_from_args(interner, trait_.into(), parent_subst); self.table.register_predicate(Obligation::new( interner, ObligationCause::new(), param_env, trait_ref, )); } } fn resolve_trait_assoc_item( &mut self, trait_ref: TraitRef<'db>, segment: PathSegment<'_>, id: ExprOrPatId, ) -> Option<(ValueNs, GenericArgs<'db>)> { let trait_ = trait_ref.def_id.0; let item = trait_.trait_items(self.db).items.iter().map(|(_name, id)| *id).find_map(|item| { match item { AssocItemId::FunctionId(func) => { if segment.name == &self.db.function_signature(func).name { Some(CandidateId::FunctionId(func)) } else { None } } AssocItemId::ConstId(konst) => { if self.db.const_signature(konst).name.as_ref() == Some(segment.name) { Some(CandidateId::ConstId(konst)) } else { None } } AssocItemId::TypeAliasId(_) => None, } })?; let def = match item { CandidateId::FunctionId(f) => ValueNs::FunctionId(f), CandidateId::ConstId(c) => ValueNs::ConstId(c), }; self.write_assoc_resolution(id, item, trait_ref.args); Some((def, trait_ref.args)) } fn resolve_ty_assoc_item( &mut self, ty: Ty<'db>, name: &Name, id: ExprOrPatId, ) -> Option<(ValueNs, GenericArgs<'db>)> { if ty.is_ty_error() { return None; } if let Some(result) = self.resolve_enum_variant_on_ty(ty, name, id) { return Some(result); } let res = self.with_method_resolution(|ctx| { ctx.probe_for_name(method_resolution::Mode::Path, name.clone(), ty) }); let (item, visible) = match res { Ok(res) => (res.item, true), Err(error) => match error { MethodError::PrivateMatch(candidate_id) => (candidate_id.item, false), _ => { self.push_diagnostic(InferenceDiagnostic::UnresolvedAssocItem { id }); return None; } }, }; let (def, container) = match item { CandidateId::FunctionId(f) => (ValueNs::FunctionId(f), f.lookup(self.db).container), CandidateId::ConstId(c) => (ValueNs::ConstId(c), c.lookup(self.db).container), }; let substs = match container { ItemContainerId::ImplId(impl_id) => { let impl_substs = self.table.fresh_args_for_item(impl_id.into()); let impl_self_ty = self.db.impl_self_ty(impl_id).instantiate(self.interner(), impl_substs); self.unify(impl_self_ty, ty); impl_substs } ItemContainerId::TraitId(trait_) => { // we're picking this method let args = GenericArgs::fill_rest( self.interner(), trait_.into(), [ty.into()], |_, id, _| self.table.next_var_for_param(id), ); let trait_ref = TraitRef::new_from_args(self.interner(), trait_.into(), args); self.table.register_predicate(Obligation::new( self.interner(), ObligationCause::new(), self.table.param_env, trait_ref, )); args } ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => { never!("assoc item contained in module/extern block"); return None; } }; self.write_assoc_resolution(id, item, substs); if !visible { let item = match item { CandidateId::FunctionId(it) => it.into(), CandidateId::ConstId(it) => it.into(), }; self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id, item }); } Some((def, substs)) } fn resolve_enum_variant_on_ty( &mut self, ty: Ty<'db>, name: &Name, id: ExprOrPatId, ) -> Option<(ValueNs, GenericArgs<'db>)> { let ty = self.table.try_structurally_resolve_type(ty); let (enum_id, subst) = match ty.as_adt() { Some((AdtId::EnumId(e), subst)) => (e, subst), _ => return None, }; let enum_data = enum_id.enum_variants(self.db); let variant = enum_data.variant(name)?; self.write_variant_resolution(id, variant.into()); Some((ValueNs::EnumVariantId(variant), subst)) } } #[derive(Debug)] enum ValuePathResolution<'db> { // It's awkward to wrap a single ID in two enums, but we need both and this saves fallible // conversion between them + `unwrap()`. GenericDef(ValueTyDefId, GenericDefId, GenericArgs<'db>), NonGeneric(Ty<'db>), }