| //! Lookup hir elements using positions in the source code. This is a lossy |
| //! transformation: in general, a single source might correspond to several |
| //! modules, functions, etc, due to macros, cfgs and `#[path=]` attributes on |
| //! modules. |
| //! |
| //! So, this modules should not be used during hir construction, it exists |
| //! purely for "IDE needs". |
| use std::{ |
| cell::OnceCell, |
| iter::{self, once}, |
| }; |
| |
| use either::Either; |
| use hir_def::{ |
| AdtId, AssocItemId, CallableDefId, ConstId, DefWithBodyId, ExpressionStoreOwnerId, FieldId, |
| FunctionId, GenericDefId, HasModule, LocalFieldId, LoweringMode, ModuleDefId, StructId, |
| VariantId, |
| expr_store::{ |
| Body, BodySourceMap, ExpressionStore, ExpressionStoreSourceMap, HygieneId, |
| lower::{ExprCollector, lower_generic_params}, |
| path::Path, |
| scope::{ExprScopes, ScopeId}, |
| }, |
| hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat, PatId, generics::GenericParams}, |
| lang_item::LangItems, |
| nameres::MacroSubNs, |
| resolver::{ResolveValueResult, Resolver, TypeNs, ValueNs, resolver_for_scope}, |
| type_ref::{Mutability, TypeRefId}, |
| visibility::Visibility, |
| }; |
| use hir_expand::{ |
| HirFileId, InFile, |
| mod_path::{ModPath, PathKind, path}, |
| name::{AsName, Name}, |
| }; |
| use hir_ty::{ |
| Adjustment, InferBodyId, InferenceResult, LifetimeElisionKind, LifetimeLoweringMode, |
| ParamEnvAndCrate, TyLoweringContext, TyLoweringInferVarsCtx, |
| diagnostics::{ |
| InsideUnsafeBlock, record_literal_missing_fields, record_pattern_missing_fields, |
| unsafe_operations, |
| }, |
| lang_items::lang_items_for_bin_op, |
| method_resolution::{self, CandidateId}, |
| next_solver::{ |
| AliasTy, DbInterner, DefaultAny, EarlyBinder, ErrorGuaranteed, GenericArgs, ParamEnv, |
| Region, Ty, TyKind, TypingMode, infer::DbInternerInferExt, |
| }, |
| traits::{WherePredicateEvaluation, structurally_normalize_ty, where_predicate_must_hold}, |
| }; |
| use intern::sym; |
| use itertools::Itertools; |
| use rustc_hash::FxHashSet; |
| use rustc_type_ir::{ |
| AliasTyKind, |
| inherent::{IntoKind, Ty as _}, |
| }; |
| use smallvec::SmallVec; |
| use stdx::never; |
| use syntax::{ |
| SyntaxKind, SyntaxNode, TextRange, TextSize, |
| ast::{self, AstNode, RangeItem, RangeOp}, |
| }; |
| |
| use crate::{ |
| Adt, AnyFunctionId, AssocItem, BindingMode, BuiltinAttr, BuiltinType, Callable, Const, |
| DeriveHelper, EnumVariant, Field, Function, GenericSubstitution, Local, Macro, ModuleDef, |
| PredicateEvaluationResult, SemanticsImpl, Static, Struct, ToolModule, Trait, TupleField, Type, |
| TypeAlias, TypeOwnerId, |
| db::HirDatabase, |
| semantics::{PathResolution, PathResolutionPerNs}, |
| }; |
| |
| /// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of |
| /// original source files. It should not be used inside the HIR itself. |
| #[derive(Debug)] |
| pub(crate) struct SourceAnalyzer<'db> { |
| pub(crate) file_id: HirFileId, |
| pub(crate) resolver: Resolver<'db>, |
| pub(crate) body_or_sig: Option<BodyOrSig<'db>>, |
| pub(crate) type_owner: TypeOwnerId<'db>, |
| pub(crate) infer_body: Option<InferBodyId<'db>>, |
| } |
| |
| #[derive(Debug)] |
| pub(crate) enum BodyOrSig<'db> { |
| Body { |
| def: DefWithBodyId, |
| body: &'db Body, |
| source_map: &'db BodySourceMap, |
| infer: Option<&'db InferenceResult<'db>>, |
| }, |
| VariantFields { |
| def: VariantId, |
| store: &'db ExpressionStore, |
| source_map: &'db ExpressionStoreSourceMap, |
| infer: Option<&'db InferenceResult<'db>>, |
| }, |
| Sig { |
| def: GenericDefId, |
| store: &'db ExpressionStore, |
| source_map: &'db ExpressionStoreSourceMap, |
| infer: Option<&'db InferenceResult<'db>>, |
| #[expect(dead_code)] |
| generics: &'db GenericParams, |
| }, |
| } |
| |
| impl<'db> SourceAnalyzer<'db> { |
| pub(crate) fn new_for_body( |
| db: &'db dyn HirDatabase, |
| def: DefWithBodyId, |
| node: InFile<&SyntaxNode>, |
| offset: Option<TextSize>, |
| ) -> SourceAnalyzer<'db> { |
| Self::new_for_body_(db, def, node, offset, Some(InferenceResult::of(db, def))) |
| } |
| |
| pub(crate) fn new_for_body_no_infer( |
| db: &'db dyn HirDatabase, |
| def: DefWithBodyId, |
| node: InFile<&SyntaxNode>, |
| offset: Option<TextSize>, |
| ) -> SourceAnalyzer<'db> { |
| Self::new_for_body_(db, def, node, offset, None) |
| } |
| |
| pub(crate) fn new_for_body_( |
| db: &'db dyn HirDatabase, |
| def: DefWithBodyId, |
| node @ InFile { file_id, .. }: InFile<&SyntaxNode>, |
| offset: Option<TextSize>, |
| infer: Option<&'db InferenceResult<'db>>, |
| ) -> SourceAnalyzer<'db> { |
| let (body, source_map) = Body::with_source_map(db, def); |
| let scopes = ExprScopes::of(db, def); |
| let scope = match offset { |
| None => scope_for(db, scopes, source_map, node), |
| Some(offset) => { |
| debug_assert!( |
| node.text_range().contains_inclusive(offset), |
| "{:?} not in {:?}", |
| offset, |
| node.text_range() |
| ); |
| scope_for_offset(db, scopes, source_map, node.file_id, offset) |
| } |
| }; |
| let (scope, _expr) = scope.unzip(); |
| let resolver = resolver_for_scope(db, def, scope); |
| SourceAnalyzer { |
| resolver, |
| body_or_sig: Some(BodyOrSig::Body { def, body, source_map, infer }), |
| file_id, |
| type_owner: def.generic_def(db).into(), |
| infer_body: Some(def.into()), |
| } |
| } |
| |
| pub(crate) fn new_generic_def( |
| db: &'db dyn HirDatabase, |
| sema: &SemanticsImpl<'db>, |
| def: GenericDefId, |
| node: InFile<&SyntaxNode>, |
| offset: Option<TextSize>, |
| ) -> SourceAnalyzer<'db> { |
| Self::new_generic_def_(db, sema, def, node, offset, true) |
| } |
| |
| pub(crate) fn new_generic_def_no_infer( |
| db: &'db dyn HirDatabase, |
| sema: &SemanticsImpl<'db>, |
| def: GenericDefId, |
| node: InFile<&SyntaxNode>, |
| offset: Option<TextSize>, |
| ) -> SourceAnalyzer<'db> { |
| Self::new_generic_def_(db, sema, def, node, offset, false) |
| } |
| |
| pub(crate) fn new_generic_def_( |
| db: &'db dyn HirDatabase, |
| sema: &SemanticsImpl<'db>, |
| def: GenericDefId, |
| node @ InFile { file_id, .. }: InFile<&SyntaxNode>, |
| offset: Option<TextSize>, |
| infer: bool, |
| ) -> SourceAnalyzer<'db> { |
| let (generics, store, source_map) = GenericParams::with_source_map(db, def); |
| let scopes = ExprScopes::of(db, def); |
| let scope = match offset { |
| None => scope_for(db, scopes, source_map, node), |
| Some(offset) => { |
| debug_assert!( |
| node.text_range().contains_inclusive(offset), |
| "{:?} not in {:?}", |
| offset, |
| node.text_range() |
| ); |
| scope_for_offset(db, scopes, source_map, node.file_id, offset) |
| } |
| }; |
| let (scope, expr) = scope.unzip(); |
| let resolver = resolver_for_scope(db, def, scope); |
| let infer_body = expr.and_then(|expr| { |
| sema.infer_body_for_expr_or_pat( |
| ExpressionStoreOwnerId::Signature(def), |
| store, |
| expr.into(), |
| ) |
| }); |
| let infer = if infer && let Some(infer_body) = infer_body { |
| Some(InferenceResult::of(db, infer_body)) |
| } else { |
| None |
| }; |
| SourceAnalyzer { |
| resolver, |
| body_or_sig: Some(BodyOrSig::Sig { def, store, source_map, generics, infer }), |
| file_id, |
| type_owner: def.into(), |
| infer_body, |
| } |
| } |
| |
| pub(crate) fn new_variant_body( |
| db: &'db dyn HirDatabase, |
| sema: &SemanticsImpl<'db>, |
| def: VariantId, |
| node @ InFile { file_id, .. }: InFile<&SyntaxNode>, |
| offset: Option<TextSize>, |
| infer: bool, |
| ) -> SourceAnalyzer<'db> { |
| let (fields, source_map) = def.fields_with_source_map(db); |
| let scopes = ExprScopes::of(db, def); |
| let scope = match offset { |
| None => scope_for(db, scopes, source_map, node), |
| Some(offset) => { |
| debug_assert!( |
| node.text_range().contains_inclusive(offset), |
| "{:?} not in {:?}", |
| offset, |
| node.text_range() |
| ); |
| scope_for_offset(db, scopes, source_map, node.file_id, offset) |
| } |
| }; |
| let (scope, expr) = scope.unzip(); |
| let resolver = resolver_for_scope(db, def, scope); |
| let infer_body = expr.and_then(|expr| { |
| sema.infer_body_for_expr_or_pat( |
| ExpressionStoreOwnerId::VariantFields(def), |
| &fields.store, |
| expr.into(), |
| ) |
| }); |
| let infer = if infer && let Some(infer_body) = infer_body { |
| Some(InferenceResult::of(db, infer_body)) |
| } else { |
| None |
| }; |
| SourceAnalyzer { |
| resolver, |
| body_or_sig: Some(BodyOrSig::VariantFields { |
| def, |
| store: &fields.store, |
| source_map, |
| infer, |
| }), |
| file_id, |
| type_owner: GenericDefId::from(def.adt_id(db)).into(), |
| infer_body, |
| } |
| } |
| |
| pub(crate) fn new_for_resolver( |
| resolver: Resolver<'db>, |
| node: InFile<&SyntaxNode>, |
| ) -> SourceAnalyzer<'db> { |
| SourceAnalyzer { |
| type_owner: resolver |
| .generic_def() |
| .map(Into::into) |
| .unwrap_or_else(|| TypeOwnerId::NoParams(resolver.krate())), |
| resolver, |
| body_or_sig: None, |
| file_id: node.file_id, |
| infer_body: None, |
| } |
| } |
| |
| fn owner(&self) -> Option<ExpressionStoreOwnerId> { |
| self.body_or_sig.as_ref().map(|it| match *it { |
| BodyOrSig::VariantFields { def, .. } => def.into(), |
| BodyOrSig::Sig { def, .. } => def.into(), |
| BodyOrSig::Body { def, .. } => def.into(), |
| }) |
| } |
| |
| fn infer(&self) -> Option<&'db InferenceResult<'db>> { |
| self.body_or_sig.as_ref().and_then(|it| match *it { |
| BodyOrSig::VariantFields { infer, .. } |
| | BodyOrSig::Sig { infer, .. } |
| | BodyOrSig::Body { infer, .. } => infer, |
| }) |
| } |
| |
| pub(crate) fn ty(&self, ty: Ty<'db>) -> Type<'db> { |
| Type { owner: self.type_owner, ty: EarlyBinder::bind(ty) } |
| } |
| |
| pub(crate) fn def( |
| &self, |
| ) -> Option<( |
| ExpressionStoreOwnerId, |
| &'db ExpressionStore, |
| &'db ExpressionStoreSourceMap, |
| Option<&'db InferenceResult<'db>>, |
| )> { |
| self.body_or_sig.as_ref().map(|it| match *it { |
| BodyOrSig::VariantFields { def, store, source_map, infer, .. } => { |
| (def.into(), store, source_map, infer) |
| } |
| BodyOrSig::Sig { def, store, source_map, infer, .. } => { |
| (def.into(), store, source_map, infer) |
| } |
| BodyOrSig::Body { def, body, source_map, infer, .. } => { |
| (def.into(), &body.store, &source_map.store, infer) |
| } |
| }) |
| } |
| |
| pub(crate) fn store(&self) -> Option<&'db ExpressionStore> { |
| self.body_or_sig.as_ref().map(|it| match *it { |
| BodyOrSig::Sig { store, .. } => store, |
| BodyOrSig::VariantFields { store, .. } => store, |
| BodyOrSig::Body { body, .. } => &body.store, |
| }) |
| } |
| |
| pub(crate) fn store_sm(&self) -> Option<&'db ExpressionStoreSourceMap> { |
| self.body_or_sig.as_ref().map(|it| match *it { |
| BodyOrSig::Sig { source_map, .. } => source_map, |
| BodyOrSig::VariantFields { source_map, .. } => source_map, |
| BodyOrSig::Body { source_map, .. } => &source_map.store, |
| }) |
| } |
| |
| fn param_and<'a>(&self, param_env: ParamEnv<'a>) -> ParamEnvAndCrate<'a> { |
| ParamEnvAndCrate { param_env, krate: self.resolver.krate() } |
| } |
| |
| fn trait_environment(&self, db: &'db dyn HirDatabase) -> ParamEnvAndCrate<'db> { |
| self.param_and(self.body_or_sig.as_ref().map_or_else( |
| || ParamEnv::empty(DbInterner::new_no_crate(db)), |
| |body_or_sig| { |
| let def = match *body_or_sig { |
| BodyOrSig::Body { def, .. } => def.generic_def(db), |
| BodyOrSig::VariantFields { def, .. } => match def { |
| VariantId::EnumVariantId(def) => def.loc(db).parent.into(), |
| VariantId::StructId(def) => def.into(), |
| VariantId::UnionId(def) => def.into(), |
| }, |
| BodyOrSig::Sig { def, .. } => def, |
| }; |
| db.trait_environment(def) |
| }, |
| )) |
| } |
| |
| pub(crate) fn evaluate_where_clause( |
| &self, |
| db: &'db dyn HirDatabase, |
| where_clause: ast::WhereClause, |
| ) -> PredicateEvaluationResult { |
| let Some(owner) = self.owner() else { |
| // FIXME |
| return PredicateEvaluationResult::unsupported( |
| "predicate evaluation is only supported inside an item", |
| ); |
| }; |
| let generic_def = owner.generic_def(db); |
| let module = generic_def.module(db); |
| let (store, params, _) = lower_generic_params( |
| db, |
| module, |
| generic_def, |
| self.file_id, |
| None, |
| Some(where_clause), |
| LoweringMode::Ide, |
| ); |
| let predicates = params.where_predicates(); |
| if predicates.is_empty() { |
| return PredicateEvaluationResult::holds("predicate does not impose any obligations"); |
| } |
| |
| let env = self.trait_environment(db); |
| for predicate in predicates { |
| match where_predicate_must_hold( |
| db, |
| &self.resolver, |
| &store, |
| owner, |
| generic_def, |
| env, |
| predicate, |
| ) { |
| WherePredicateEvaluation::Holds | WherePredicateEvaluation::NoObligations => {} |
| WherePredicateEvaluation::HasErrors => { |
| return PredicateEvaluationResult::invalid( |
| "predicate contains unresolved names or invalid type syntax", |
| ); |
| } |
| WherePredicateEvaluation::NotProven => { |
| return PredicateEvaluationResult::not_proven("predicate is not known to hold"); |
| } |
| } |
| } |
| |
| PredicateEvaluationResult::holds("predicate holds") |
| } |
| |
| pub(crate) fn expr_id(&self, expr: ast::Expr) -> Option<ExprOrPatId> { |
| let src = InFile { file_id: self.file_id, value: expr }; |
| self.store_sm()?.node_expr(src.as_ref()) |
| } |
| |
| fn pat_id(&self, pat: &ast::Pat) -> Option<ExprOrPatId> { |
| let src = InFile { file_id: self.file_id, value: pat }; |
| self.store_sm()?.node_pat(src) |
| } |
| |
| fn type_id(&self, pat: &ast::Type) -> Option<TypeRefId> { |
| let src = InFile { file_id: self.file_id, value: pat }; |
| self.store_sm()?.node_type(src) |
| } |
| |
| fn binding_id_of_pat(&self, pat: &ast::IdentPat) -> Option<BindingId> { |
| let pat_id = self.pat_id(&pat.clone().into())?; |
| if let Pat::Bind { id, .. } = self.store()?[pat_id.as_pat()?] { Some(id) } else { None } |
| } |
| |
| pub(crate) fn expr_adjustments(&self, expr: &ast::Expr) -> Option<&[Adjustment]> { |
| // It is safe to omit destructuring assignments here because they have no adjustments (neither |
| // expressions nor patterns). |
| let expr_id = self.expr_id(expr.clone())?.as_expr()?; |
| let infer = self.infer()?; |
| infer.expr_adjustment(expr_id) |
| } |
| |
| pub(crate) fn type_of_type( |
| &self, |
| db: &'db dyn HirDatabase, |
| ty: &ast::Type, |
| ) -> Option<Type<'db>> { |
| let interner = DbInterner::new_no_crate(db); |
| |
| let type_ref = self.type_id(ty)?; |
| |
| let generic_def = self.resolver.generic_def()?; |
| let generics = OnceCell::new(); |
| let mut vars_cts = VarsCtx { types: interner.default_types(), infer: self.infer() }; |
| let ty = TyLoweringContext::new( |
| db, |
| &self.resolver, |
| self.store()?, |
| generic_def.into(), |
| generic_def, |
| &generics, |
| // FIXME: Is this correct here? Anyway that should impact mostly diagnostics, which we don't emit here |
| // (this can impact the lifetimes generated, e.g. in `const` they won't be `'static`, but this seems like a |
| // small problem). |
| LifetimeElisionKind::Infer, |
| LifetimeLoweringMode::LateParam, |
| ) |
| .with_infer_vars_behavior(Some(&mut vars_cts)) |
| .lower_ty(type_ref); |
| |
| struct VarsCtx<'a, 'db> { |
| types: &'a DefaultAny<'db>, |
| infer: Option<&'a InferenceResult<'db>>, |
| } |
| |
| impl<'db> TyLoweringInferVarsCtx<'db> for VarsCtx<'_, 'db> { |
| fn next_ty_var(&mut self, span: hir_ty::Span) -> Ty<'db> { |
| if let hir_ty::Span::TypeRefId(type_ref) = span |
| && let Some(ty) = |
| self.infer.and_then(|infer| infer.type_of_type_placeholder(type_ref)) |
| { |
| ty |
| } else { |
| self.types.types.error |
| } |
| } |
| fn next_const_var(&mut self, _span: hir_ty::Span) -> hir_ty::next_solver::Const<'db> { |
| self.types.consts.error |
| } |
| fn next_region_var(&mut self, _span: hir_ty::Span) -> Region<'db> { |
| self.types.regions.error |
| } |
| } |
| |
| Some(self.ty(ty)) |
| } |
| |
| pub(crate) fn expr_is_diverging( |
| &self, |
| _db: &'db dyn HirDatabase, |
| expr: &ast::Expr, |
| ) -> Option<bool> { |
| let expr_id = self.expr_id(expr.clone())?; |
| let store = self.store()?; |
| let infer = self.infer()?; |
| Some(self.expr_id_is_diverging(store, infer, expr_id)) |
| } |
| |
| fn expr_id_is_diverging( |
| &self, |
| store: &ExpressionStore, |
| infer: &InferenceResult<'_>, |
| expr_id: ExprOrPatId, |
| ) -> bool { |
| // FIXME: This is an approximation, perhaps we need to store a set of diverging exprs in inference? |
| if infer.type_of_expr_or_pat(expr_id).is_some_and(|ty| ty.is_never()) { |
| true |
| } else if let ExprOrPatId::ExprId(expr_id) = expr_id |
| && let Expr::Block { tail: Some(tail), .. } = store[expr_id] |
| { |
| self.expr_id_is_diverging(store, infer, tail.into()) |
| } else { |
| false |
| } |
| } |
| |
| pub(crate) fn type_of_expr( |
| &self, |
| _db: &'db dyn HirDatabase, |
| expr: &ast::Expr, |
| ) -> Option<(Type<'db>, Option<Type<'db>>)> { |
| let expr_id = self.expr_id(expr.clone())?; |
| let infer = self.infer()?; |
| let coerced = expr_id |
| .as_expr() |
| .and_then(|expr_id| infer.expr_adjustment(expr_id)) |
| .and_then(|adjusts| adjusts.last().map(|adjust| adjust.target.as_ref())); |
| let ty = infer.expr_or_pat_ty(expr_id); |
| let mk_ty = |ty: Ty<'db>| self.ty(ty); |
| Some((mk_ty(ty), coerced.map(mk_ty))) |
| } |
| |
| pub(crate) fn type_of_pat( |
| &self, |
| _db: &'db dyn HirDatabase, |
| pat: &ast::Pat, |
| ) -> Option<(Type<'db>, Option<Type<'db>>)> { |
| let expr_or_pat_id = self.pat_id(pat)?; |
| let infer = self.infer()?; |
| let coerced = match expr_or_pat_id { |
| ExprOrPatId::ExprId(idx) => infer |
| .expr_adjustment(idx) |
| .and_then(|adjusts| adjusts.last()) |
| .map(|adjust| adjust.target.as_ref()), |
| ExprOrPatId::PatId(idx) => infer |
| .pat_adjustment(idx) |
| .and_then(|adjusts| adjusts.last()) |
| .map(|adjust| adjust.source.as_ref()), |
| }; |
| |
| let ty = infer.expr_or_pat_ty(expr_or_pat_id); |
| let mk_ty = |ty: Ty<'db>| self.ty(ty); |
| Some((mk_ty(ty), coerced.map(mk_ty))) |
| } |
| |
| pub(crate) fn type_of_binding_in_pat( |
| &self, |
| _db: &'db dyn HirDatabase, |
| pat: &ast::IdentPat, |
| ) -> Option<Type<'db>> { |
| let binding_id = self.binding_id_of_pat(pat)?; |
| let infer = self.infer()?; |
| let ty = infer.binding_ty(binding_id); |
| let mk_ty = |ty: Ty<'db>| self.ty(ty); |
| Some(mk_ty(ty)) |
| } |
| |
| pub(crate) fn type_of_self( |
| &self, |
| _db: &'db dyn HirDatabase, |
| _param: &ast::SelfParam, |
| ) -> Option<Type<'db>> { |
| let binding = match self.body_or_sig.as_ref()? { |
| BodyOrSig::Sig { .. } | BodyOrSig::VariantFields { .. } => return None, |
| BodyOrSig::Body { body, .. } => body.self_param?.formal, |
| }; |
| let ty = self.infer()?.binding_ty(binding); |
| Some(self.ty(ty)) |
| } |
| |
| pub(crate) fn binding_mode_of_pat( |
| &self, |
| _db: &'db dyn HirDatabase, |
| pat: &ast::IdentPat, |
| ) -> Option<BindingMode> { |
| let id = self.pat_id(&pat.clone().into())?; |
| let infer = self.infer()?; |
| Some(match infer.binding_mode(id.as_pat()?)? { |
| hir_ty::BindingMode(hir_ty::ByRef::No, _) => BindingMode::Move, |
| hir_ty::BindingMode(hir_ty::ByRef::Yes(hir_ty::next_solver::Mutability::Mut), _) => { |
| BindingMode::Ref(Mutability::Mut) |
| } |
| hir_ty::BindingMode(hir_ty::ByRef::Yes(hir_ty::next_solver::Mutability::Not), _) => { |
| BindingMode::Ref(Mutability::Shared) |
| } |
| }) |
| } |
| pub(crate) fn pattern_adjustments( |
| &self, |
| _db: &'db dyn HirDatabase, |
| pat: &ast::Pat, |
| ) -> Option<SmallVec<[Type<'db>; 1]>> { |
| let pat_id = self.pat_id(pat)?; |
| let infer = self.infer()?; |
| Some( |
| infer |
| .pat_adjustment(pat_id.as_pat()?)? |
| .iter() |
| .map(|adjust| self.ty(adjust.source.as_ref())) |
| .collect(), |
| ) |
| } |
| |
| pub(crate) fn resolve_method_call_as_callable( |
| &self, |
| db: &'db dyn HirDatabase, |
| call: &ast::MethodCallExpr, |
| ) -> Option<Callable<'db>> { |
| let expr_id = self.expr_id(call.clone().into())?.as_expr()?; |
| let (func, args) = self.infer()?.method_resolution(expr_id)?; |
| let interner = DbInterner::new_no_crate(db); |
| let ty = db.value_ty(func.into())?.instantiate(interner, args).skip_norm_wip(); |
| let ty = self.ty(ty); |
| let mut res = ty.as_callable(db)?; |
| res.is_bound_method = true; |
| Some(res) |
| } |
| |
| pub(crate) fn resolve_method_call( |
| &self, |
| db: &'db dyn HirDatabase, |
| call: &ast::MethodCallExpr, |
| ) -> Option<Function> { |
| let expr_id = self.expr_id(call.clone().into())?.as_expr()?; |
| let (f_in_trait, substs) = self.infer()?.method_resolution(expr_id)?; |
| |
| Some(self.resolve_impl_method_or_trait_def(db, f_in_trait, substs)) |
| } |
| |
| pub(crate) fn resolve_method_call_fallback( |
| &self, |
| db: &'db dyn HirDatabase, |
| call: &ast::MethodCallExpr, |
| ) -> Option<(Either<Function, Field>, Option<GenericSubstitution<'db>>)> { |
| let expr_id = self.expr_id(call.clone().into())?.as_expr()?; |
| let inference_result = self.infer()?; |
| match inference_result.method_resolution(expr_id) { |
| Some((f_in_trait, substs)) => { |
| let (fn_, subst) = |
| self.resolve_impl_method_or_trait_def_with_subst(db, f_in_trait, substs); |
| Some(( |
| Either::Left(fn_), |
| GenericSubstitution::new_from_fn(fn_, subst, self.type_owner), |
| )) |
| } |
| None => { |
| inference_result.field_resolution(expr_id).and_then(Either::left).map(|field| { |
| (Either::Right(field.into()), self.field_subst(expr_id, inference_result, db)) |
| }) |
| } |
| } |
| } |
| |
| pub(crate) fn resolve_expr_as_callable( |
| &self, |
| db: &'db dyn HirDatabase, |
| call: &ast::Expr, |
| ) -> Option<Callable<'db>> { |
| let (orig, adjusted) = self.type_of_expr(db, &call.clone())?; |
| adjusted.unwrap_or(orig).as_callable(db) |
| } |
| |
| pub(crate) fn resolve_field( |
| &self, |
| field: &ast::FieldExpr, |
| ) -> Option<Either<Field, TupleField<'db>>> { |
| let def = self.infer_body?; |
| let expr_id = self.expr_id(field.clone().into())?.as_expr()?; |
| self.infer()?.field_resolution(expr_id).map(|it| { |
| it.map_either(Into::into, |f| TupleField { owner: def, tuple: f.tuple, index: f.index }) |
| }) |
| } |
| |
| fn field_subst( |
| &self, |
| field_expr: ExprId, |
| infer: &InferenceResult<'_>, |
| _db: &'db dyn HirDatabase, |
| ) -> Option<GenericSubstitution<'db>> { |
| let body = self.store()?; |
| if let Expr::Field { expr: object_expr, name: _ } = body[field_expr] { |
| let (adt, subst) = infer.type_of_expr_with_adjust(object_expr)?.as_adt()?; |
| return Some(GenericSubstitution::new(adt.into(), subst, self.type_owner)); |
| } |
| None |
| } |
| |
| pub(crate) fn resolve_field_fallback( |
| &self, |
| db: &'db dyn HirDatabase, |
| field: &ast::FieldExpr, |
| ) -> Option<(Either<Either<Field, TupleField<'db>>, Function>, Option<GenericSubstitution<'db>>)> |
| { |
| let def = self.infer_body?; |
| let expr_id = self.expr_id(field.clone().into())?.as_expr()?; |
| let inference_result = self.infer()?; |
| match inference_result.field_resolution(expr_id) { |
| Some(field) => match field { |
| Either::Left(field) => Some(( |
| Either::Left(Either::Left(field.into())), |
| self.field_subst(expr_id, inference_result, db), |
| )), |
| Either::Right(field) => Some(( |
| Either::Left(Either::Right(TupleField { |
| owner: def, |
| tuple: field.tuple, |
| index: field.index, |
| })), |
| None, |
| )), |
| }, |
| None => inference_result.method_resolution(expr_id).map(|(f, substs)| { |
| let (f, subst) = self.resolve_impl_method_or_trait_def_with_subst(db, f, substs); |
| (Either::Right(f), GenericSubstitution::new_from_fn(f, subst, self.type_owner)) |
| }), |
| } |
| } |
| |
| pub(crate) fn resolve_range_pat( |
| &self, |
| db: &'db dyn HirDatabase, |
| range_pat: &ast::RangePat, |
| ) -> Option<StructId> { |
| self.resolve_range_struct( |
| db, |
| range_pat.op_kind()?, |
| range_pat.start().is_some(), |
| range_pat.end().is_some(), |
| ) |
| } |
| |
| pub(crate) fn resolve_range_expr( |
| &self, |
| db: &'db dyn HirDatabase, |
| range_expr: &ast::RangeExpr, |
| ) -> Option<StructId> { |
| self.resolve_range_struct( |
| db, |
| range_expr.op_kind()?, |
| range_expr.start().is_some(), |
| range_expr.end().is_some(), |
| ) |
| } |
| |
| fn resolve_range_struct( |
| &self, |
| db: &'db dyn HirDatabase, |
| op_kind: RangeOp, |
| has_start: bool, |
| has_end: bool, |
| ) -> Option<StructId> { |
| let has_new_range = self.resolver.top_level_def_map().features().new_range; |
| let lang_items = self.lang_items(db); |
| match (op_kind, has_start, has_end) { |
| (RangeOp::Exclusive, false, false) => lang_items.RangeFull, |
| (RangeOp::Exclusive, false, true) => lang_items.RangeTo, |
| (RangeOp::Exclusive, true, false) => { |
| if has_new_range { |
| lang_items.RangeFromCopy |
| } else { |
| lang_items.RangeFrom |
| } |
| } |
| (RangeOp::Exclusive, true, true) => { |
| if has_new_range { |
| lang_items.RangeCopy |
| } else { |
| lang_items.Range |
| } |
| } |
| (RangeOp::Inclusive, false, true) => { |
| if has_new_range { |
| lang_items.RangeToInclusiveCopy |
| } else { |
| lang_items.RangeToInclusive |
| } |
| } |
| (RangeOp::Inclusive, true, true) => { |
| if has_new_range { |
| lang_items.RangeInclusiveCopy |
| } else { |
| lang_items.RangeInclusiveStruct |
| } |
| } |
| // [E0586] inclusive ranges must be bounded at the end |
| (RangeOp::Inclusive, false, false) => None, |
| (RangeOp::Inclusive, true, false) => None, |
| } |
| } |
| |
| pub(crate) fn resolve_await_to_poll( |
| &self, |
| db: &'db dyn HirDatabase, |
| await_expr: &ast::AwaitExpr, |
| ) -> Option<Function> { |
| let mut ty = self.ty_of_expr(await_expr.expr()?)?; |
| |
| let into_future_trait = self |
| .resolver |
| .resolve_known_trait(db, &path![core::future::IntoFuture]) |
| .map(Trait::from); |
| |
| if let Some(into_future_trait) = into_future_trait { |
| let type_ = self.ty(ty); |
| if type_.impls_trait(db, into_future_trait, &[]) { |
| let items = into_future_trait.items(db); |
| let into_future_type = items.into_iter().find_map(|item| match item { |
| AssocItem::TypeAlias(alias) |
| if alias.name(db) == Name::new_symbol_root(sym::IntoFuture) => |
| { |
| Some(alias) |
| } |
| _ => None, |
| })?; |
| let future_trait = type_.normalize_trait_assoc_type(db, &[], into_future_type)?; |
| ty = future_trait.ty.skip_binder(); |
| } |
| } |
| |
| let poll_fn = self.lang_items(db).FuturePoll?; |
| // HACK: subst for `poll()` coincides with that for `Future` because `poll()` itself |
| // doesn't have any generic parameters, so we skip building another subst for `poll()`. |
| let substs = GenericArgs::new_from_slice(&[ty.into()]); |
| Some(self.resolve_impl_method_or_trait_def(db, poll_fn, substs)) |
| } |
| |
| pub(crate) fn resolve_prefix_expr( |
| &self, |
| db: &'db dyn HirDatabase, |
| prefix_expr: &ast::PrefixExpr, |
| ) -> Option<Function> { |
| let lang_items = self.lang_items(db); |
| let (_op_trait, op_fn) = match prefix_expr.op_kind()? { |
| ast::UnaryOp::Deref => { |
| // This can be either `Deref::deref` or `DerefMut::deref_mut`. |
| // Since deref kind is inferenced and stored in `InferenceResult.method_resolution`, |
| // use that result to find out which one it is. |
| let (deref_trait, deref) = (lang_items.Deref?, lang_items.Deref_deref?); |
| self.infer() |
| .and_then(|infer| { |
| let expr = self.expr_id(prefix_expr.clone().into())?.as_expr()?; |
| let (func, _) = infer.method_resolution(expr)?; |
| let (deref_mut_trait, deref_mut) = |
| (lang_items.DerefMut?, lang_items.DerefMut_deref_mut?); |
| if func == deref_mut { Some((deref_mut_trait, deref_mut)) } else { None } |
| }) |
| .unwrap_or((deref_trait, deref)) |
| } |
| ast::UnaryOp::Not => (lang_items.Not?, lang_items.Not_not?), |
| ast::UnaryOp::Neg => (lang_items.Neg?, lang_items.Neg_neg?), |
| }; |
| |
| let ty = self.ty_of_expr(prefix_expr.expr()?)?; |
| |
| // HACK: subst for all methods coincides with that for their trait because the methods |
| // don't have any generic parameters, so we skip building another subst for the methods. |
| let substs = GenericArgs::new_from_slice(&[ty.into()]); |
| |
| Some(self.resolve_impl_method_or_trait_def(db, op_fn, substs)) |
| } |
| |
| pub(crate) fn resolve_index_expr( |
| &self, |
| db: &'db dyn HirDatabase, |
| index_expr: &ast::IndexExpr, |
| ) -> Option<Function> { |
| let base_ty = self.ty_of_expr(index_expr.base()?)?; |
| let index_ty = self.ty_of_expr(index_expr.index()?)?; |
| let lang_items = self.lang_items(db); |
| |
| let (_index_trait, index_fn) = (lang_items.Index?, lang_items.Index_index?); |
| let op_fn = self |
| .infer() |
| .and_then(|infer| { |
| let expr = self.expr_id(index_expr.clone().into())?.as_expr()?; |
| let (func, _) = infer.method_resolution(expr)?; |
| let (_index_mut_trait, index_mut_fn) = |
| (lang_items.IndexMut_index_mut?, lang_items.IndexMut_index_mut?); |
| if func == index_mut_fn { Some(index_mut_fn) } else { None } |
| }) |
| .unwrap_or(index_fn); |
| // HACK: subst for all methods coincides with that for their trait because the methods |
| // don't have any generic parameters, so we skip building another subst for the methods. |
| let substs = GenericArgs::new_from_slice(&[base_ty.into(), index_ty.into()]); |
| Some(self.resolve_impl_method_or_trait_def(db, op_fn, substs)) |
| } |
| |
| pub(crate) fn resolve_bin_expr( |
| &self, |
| db: &'db dyn HirDatabase, |
| binop_expr: &ast::BinExpr, |
| ) -> Option<Function> { |
| let op = binop_expr.op_kind()?; |
| let lhs = self.ty_of_expr(binop_expr.lhs()?)?; |
| let rhs = self.ty_of_expr(binop_expr.rhs()?)?; |
| |
| let (op_fn, _op_trait) = lang_items_for_bin_op(self.lang_items(db), op) |
| .and_then(|(method, trait_)| method.zip(trait_))?; |
| // HACK: subst for `index()` coincides with that for `Index` because `index()` itself |
| // doesn't have any generic parameters, so we skip building another subst for `index()`. |
| let substs = GenericArgs::new_from_slice(&[lhs.into(), rhs.into()]); |
| |
| Some(self.resolve_impl_method_or_trait_def(db, op_fn, substs)) |
| } |
| |
| pub(crate) fn resolve_try_expr( |
| &self, |
| db: &'db dyn HirDatabase, |
| try_expr: &ast::TryExpr, |
| ) -> Option<Function> { |
| let ty = self.ty_of_expr(try_expr.expr()?)?; |
| |
| let op_fn = self.lang_items(db).TryTraitBranch?; |
| // HACK: subst for `branch()` coincides with that for `Try` because `branch()` itself |
| // doesn't have any generic parameters, so we skip building another subst for `branch()`. |
| let substs = GenericArgs::new_from_slice(&[ty.into()]); |
| |
| Some(self.resolve_impl_method_or_trait_def(db, op_fn, substs)) |
| } |
| |
| pub(crate) fn resolve_record_field( |
| &self, |
| db: &'db dyn HirDatabase, |
| field: &ast::RecordExprField, |
| ) -> Option<(Field, Option<Local<'db>>, Type<'db>, GenericSubstitution<'db>)> { |
| let record_expr = ast::RecordExpr::cast(field.syntax().parent().and_then(|p| p.parent())?)?; |
| let expr = ast::Expr::from(record_expr); |
| let expr_id = self.store_sm()?.node_expr(InFile::new(self.file_id, &expr))?; |
| let interner = DbInterner::new_no_crate(db); |
| |
| let ast_name = field.field_name()?; |
| let local_name = ast_name.as_name(); |
| let local = if field.name_ref().is_some() { |
| None |
| } else { |
| // Shorthand syntax, resolve to the local |
| let path = Path::from_known_path_with_no_generic(ModPath::from_segments( |
| PathKind::Plain, |
| once(local_name.clone()), |
| )); |
| match self.resolver.resolve_path_in_value_ns_fully( |
| db, |
| &path, |
| name_hygiene(db, InFile::new(self.file_id, ast_name.syntax())), |
| ) { |
| Some(ValueNs::LocalBinding(binding_id)) => Some(Local { |
| binding_id, |
| parent: self.owner()?, |
| parent_infer: self.infer_body?, |
| }), |
| _ => None, |
| } |
| }; |
| let (adt, subst) = self.infer()?.type_of_expr_or_pat(expr_id)?.as_adt()?; |
| let variant = self.infer()?.variant_resolution_for_expr_or_pat(expr_id)?; |
| let variant_data = variant.fields(db); |
| let field = FieldId { parent: variant, local_id: variant_data.field(&local_name)? }; |
| let field_ty = (*db.field_types(variant).get(field.local_id)?) |
| .ty() |
| .instantiate(interner, subst) |
| .skip_norm_wip(); |
| Some(( |
| field.into(), |
| local, |
| self.ty(field_ty), |
| GenericSubstitution::new(adt.into(), subst, self.type_owner), |
| )) |
| } |
| |
| pub(crate) fn resolve_record_pat_field( |
| &self, |
| db: &'db dyn HirDatabase, |
| field: &ast::RecordPatField, |
| ) -> Option<(Field, Type<'db>, GenericSubstitution<'db>)> { |
| let interner = DbInterner::new_no_crate(db); |
| let field_name = field.field_name()?.as_name(); |
| let record_pat = ast::RecordPat::cast(field.syntax().parent().and_then(|p| p.parent())?)?; |
| let pat_id = self.pat_id(&record_pat.into())?; |
| let variant = self.infer()?.variant_resolution_for_pat(pat_id.as_pat()?)?; |
| let variant_data = variant.fields(db); |
| let field = FieldId { parent: variant, local_id: variant_data.field(&field_name)? }; |
| let (adt, subst) = self.infer()?.pat_ty(pat_id.as_pat()?).as_adt()?; |
| let field_ty = (*db.field_types(variant).get(field.local_id)?) |
| .ty() |
| .instantiate(interner, subst) |
| .skip_norm_wip(); |
| Some(( |
| field.into(), |
| self.ty(field_ty), |
| GenericSubstitution::new(adt.into(), subst, self.type_owner), |
| )) |
| } |
| |
| pub(crate) fn resolve_tuple_struct_pat_fields( |
| &self, |
| db: &'db dyn HirDatabase, |
| tuple_struct_pat: &ast::TupleStructPat, |
| ) -> Option<Vec<(Field, Type<'db>)>> { |
| let interner = DbInterner::new_no_crate(db); |
| let pat_id = self.pat_id(&tuple_struct_pat.clone().into())?; |
| let variant_id = self.infer()?.variant_resolution_for_pat(pat_id.as_pat()?)?; |
| let (_adt, substs) = self.infer()?.pat_ty(pat_id.as_pat()?).as_adt()?; |
| |
| Some( |
| db.field_types(variant_id) |
| .iter() |
| .map(|(local_id, field)| { |
| let def = Field { parent: variant_id.into(), id: local_id }; |
| let ty = field.ty().instantiate(interner, substs).skip_norm_wip(); |
| (def, self.ty(ty)) |
| }) |
| .collect(), |
| ) |
| } |
| |
| pub(crate) fn resolve_bind_pat_to_const( |
| &self, |
| db: &'db dyn HirDatabase, |
| pat: &ast::IdentPat, |
| ) -> Option<ModuleDef> { |
| let expr_or_pat_id = self.pat_id(&pat.clone().into())?; |
| let store = self.store()?; |
| |
| let path = match expr_or_pat_id { |
| ExprOrPatId::ExprId(idx) => match &store[idx] { |
| Expr::Path(path) => path, |
| _ => return None, |
| }, |
| ExprOrPatId::PatId(idx) => match &store[idx] { |
| Pat::Path(path) => path, |
| _ => return None, |
| }, |
| }; |
| |
| let store_owner = self.resolver.expression_store_owner(); |
| let (res, _) = resolve_hir_value_path( |
| db, |
| &self.resolver, |
| store_owner, |
| self.infer_body, |
| path, |
| HygieneId::ROOT, |
| )?; |
| match res { |
| PathResolution::Def(def) => Some(def), |
| _ => None, |
| } |
| } |
| |
| pub(crate) fn resolve_use_type_arg(&self, name: &ast::NameRef) -> Option<crate::TypeParam> { |
| let name = name.as_name(); |
| self.resolver |
| .all_generic_params() |
| .find_map(|(params, parent)| params.find_type_by_name(&name, parent)) |
| .map(crate::TypeParam::from) |
| } |
| |
| pub(crate) fn resolve_offset_of_field( |
| &self, |
| db: &'db dyn HirDatabase, |
| name_ref: &ast::NameRef, |
| ) -> Option<(Either<crate::EnumVariant, crate::Field>, GenericSubstitution<'db>)> { |
| let offset_of_expr = ast::OffsetOfExpr::cast(name_ref.syntax().parent()?)?; |
| let container = offset_of_expr.ty()?; |
| let container = self.type_of_type(db, &container)?; |
| |
| let env = self.trait_environment(db); |
| |
| let interner = DbInterner::new_with(db, env.krate); |
| let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis); |
| |
| let mut container = Either::Right(container.ty.skip_binder()); |
| for field_name in offset_of_expr.fields() { |
| if let Either::Right(container) = &mut container { |
| *container = structurally_normalize_ty(&infcx, *container, env.param_env); |
| } |
| let handle_variants = |
| |variant: VariantId, subst: GenericArgs<'db>, container: &mut _| { |
| let fields = variant.fields(db); |
| let field = fields.field(&field_name.as_name())?; |
| let field_types = db.field_types(variant); |
| *container = Either::Right( |
| field_types[field].ty().instantiate(interner, subst).skip_norm_wip(), |
| ); |
| let generic_def = match variant { |
| VariantId::EnumVariantId(it) => it.loc(db).parent.into(), |
| VariantId::StructId(it) => it.into(), |
| VariantId::UnionId(it) => it.into(), |
| }; |
| Some(( |
| Either::Right(Field { parent: variant.into(), id: field }), |
| generic_def, |
| subst, |
| )) |
| }; |
| let temp_ty = Ty::new_error(interner, ErrorGuaranteed); |
| let (field_def, generic_def, subst) = |
| match std::mem::replace(&mut container, Either::Right(temp_ty)) { |
| Either::Left((variant_id, subst)) => { |
| handle_variants(VariantId::from(variant_id), subst, &mut container)? |
| } |
| Either::Right(container_ty) => match container_ty.kind() { |
| TyKind::Adt(adt_def, subst) => match adt_def.def_id() { |
| AdtId::StructId(id) => { |
| handle_variants(id.into(), subst, &mut container)? |
| } |
| AdtId::UnionId(id) => { |
| handle_variants(id.into(), subst, &mut container)? |
| } |
| AdtId::EnumId(id) => { |
| let variants = id.enum_variants(db); |
| let variant = variants.variant(&field_name.as_name())?; |
| container = Either::Left((variant, subst)); |
| (Either::Left(EnumVariant { id: variant }), id.into(), subst) |
| } |
| }, |
| _ => return None, |
| }, |
| }; |
| |
| if field_name.syntax().text_range() == name_ref.syntax().text_range() { |
| return Some(( |
| field_def, |
| GenericSubstitution::new(generic_def, subst, self.type_owner), |
| )); |
| } |
| } |
| never!("the `NameRef` is a child of the `OffsetOfExpr`, we should've visited it"); |
| None |
| } |
| |
| pub(crate) fn resolve_path( |
| &self, |
| db: &'db dyn HirDatabase, |
| path: &ast::Path, |
| ) -> Option<(PathResolution<'db>, Option<GenericSubstitution<'db>>)> { |
| let parent = path.syntax().parent(); |
| let parent = || parent.clone(); |
| |
| let mut prefer_value_ns = parent().and_then(ast::PathExpr::cast).is_some(); |
| let resolved = (|| { |
| let infer = self.infer()?; |
| if let Some(path_expr) = parent().and_then(ast::PathExpr::cast) { |
| let expr_id = self.expr_id(path_expr.into())?; |
| if let Some((assoc, subs)) = infer.assoc_resolutions_for_expr_or_pat(expr_id) { |
| let (assoc, subst) = match assoc { |
| CandidateId::FunctionId(f_in_trait) => { |
| match infer.type_of_expr_or_pat(expr_id) { |
| None => { |
| let subst = GenericSubstitution::new( |
| f_in_trait.into(), |
| subs, |
| self.type_owner, |
| ); |
| (AssocItem::Function(f_in_trait.into()), Some(subst)) |
| } |
| Some(func_ty) => { |
| if let TyKind::FnDef(_fn_def, subs) = func_ty.kind() { |
| let (fn_, subst) = self |
| .resolve_impl_method_or_trait_def_with_subst( |
| db, f_in_trait, subs, |
| ); |
| let subst = GenericSubstitution::new_from_fn( |
| fn_, |
| subst, |
| self.type_owner, |
| ); |
| (AssocItem::Function(fn_), subst) |
| } else { |
| let subst = GenericSubstitution::new( |
| f_in_trait.into(), |
| subs, |
| self.type_owner, |
| ); |
| (AssocItem::Function(f_in_trait.into()), Some(subst)) |
| } |
| } |
| } |
| } |
| CandidateId::ConstId(const_id) => { |
| let (konst, subst) = |
| self.resolve_impl_const_or_trait_def_with_subst(db, const_id, subs); |
| let subst = |
| GenericSubstitution::new(konst.into(), subst, self.type_owner); |
| (AssocItem::Const(konst.into()), Some(subst)) |
| } |
| }; |
| |
| return Some((PathResolution::Def(assoc.into()), subst)); |
| } |
| if let Some(VariantId::EnumVariantId(variant)) = |
| infer.variant_resolution_for_expr_or_pat(expr_id) |
| { |
| return Some(( |
| PathResolution::Def(ModuleDef::EnumVariant(variant.into())), |
| None, |
| )); |
| } |
| prefer_value_ns = true; |
| } else if let Some(path_pat) = parent().and_then(ast::PathPat::cast) { |
| let expr_or_pat_id = self.pat_id(&path_pat.into())?; |
| if let Some((assoc, subs)) = infer.assoc_resolutions_for_expr_or_pat(expr_or_pat_id) |
| { |
| let (assoc, subst) = match assoc { |
| CandidateId::ConstId(const_id) => { |
| let (konst, subst) = |
| self.resolve_impl_const_or_trait_def_with_subst(db, const_id, subs); |
| let subst = |
| GenericSubstitution::new(konst.into(), subst, self.type_owner); |
| (AssocItemId::from(konst), subst) |
| } |
| CandidateId::FunctionId(function_id) => ( |
| function_id.into(), |
| GenericSubstitution::new(function_id.into(), subs, self.type_owner), |
| ), |
| }; |
| return Some((PathResolution::Def(AssocItem::from(assoc).into()), Some(subst))); |
| } |
| if let Some(VariantId::EnumVariantId(variant)) = |
| infer.variant_resolution_for_expr_or_pat(expr_or_pat_id) |
| { |
| return Some(( |
| PathResolution::Def(ModuleDef::EnumVariant(variant.into())), |
| None, |
| )); |
| } |
| } else if let Some(rec_lit) = parent().and_then(ast::RecordExpr::cast) { |
| let expr_id = self.expr_id(rec_lit.into())?; |
| if let Some(VariantId::EnumVariantId(variant)) = |
| infer.variant_resolution_for_expr_or_pat(expr_id) |
| { |
| return Some(( |
| PathResolution::Def(ModuleDef::EnumVariant(variant.into())), |
| None, |
| )); |
| } |
| } else { |
| let record_pat = parent().and_then(ast::RecordPat::cast).map(ast::Pat::from); |
| let tuple_struct_pat = |
| || parent().and_then(ast::TupleStructPat::cast).map(ast::Pat::from); |
| if let Some(pat) = record_pat.or_else(tuple_struct_pat) { |
| let pat_id = self.pat_id(&pat)?; |
| let variant_res_for_pat = infer.variant_resolution_for_pat(pat_id.as_pat()?); |
| if let Some(VariantId::EnumVariantId(variant)) = variant_res_for_pat { |
| return Some(( |
| PathResolution::Def(ModuleDef::EnumVariant(variant.into())), |
| None, |
| )); |
| } |
| } |
| } |
| None |
| })(); |
| if resolved.is_some() { |
| return resolved; |
| } |
| |
| // FIXME: collectiong here shouldnt be necessary? |
| let mut collector = |
| ExprCollector::new(db, self.resolver.module(), self.file_id, LoweringMode::Ide); |
| let hir_path = |
| collector.lower_path(path.clone(), &mut ExprCollector::impl_trait_error_allocator)?; |
| let parent_hir_path = path |
| .parent_path() |
| .and_then(|p| collector.lower_path(p, &mut ExprCollector::impl_trait_error_allocator)); |
| let (store, _) = collector.store.finish(); |
| |
| // Case where path is a qualifier of a use tree, e.g. foo::bar::{Baz, Qux} where we are |
| // trying to resolve foo::bar. |
| if let Some(use_tree) = parent().and_then(ast::UseTree::cast) |
| && use_tree.coloncolon_token().is_some() |
| { |
| return resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &store) |
| .map(|it| (it, None)); |
| } |
| |
| let meta_path = path |
| .syntax() |
| .ancestors() |
| .take_while(|it| { |
| let kind = it.kind(); |
| ast::Path::can_cast(kind) || ast::Meta::can_cast(kind) |
| }) |
| .last() |
| .and_then(ast::Meta::cast); |
| |
| // Case where path is a qualifier of another path, e.g. foo::bar::Baz where we are |
| // trying to resolve foo::bar. |
| if let Some(parent_hir_path) = parent_hir_path { |
| return match resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &store) { |
| None if meta_path.is_some() => path |
| .first_segment() |
| .and_then(|it| it.name_ref()) |
| .and_then(|name_ref| { |
| ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text()) |
| .map(PathResolution::ToolModule) |
| }) |
| .map(|it| (it, None)), |
| // Case the type name conflict with use module, |
| // e.g. |
| // ``` |
| // use std::str; |
| // fn main() { |
| // str::from_utf8(); // as module std::str |
| // str::len(); // as primitive type str |
| // str::no_exist_item(); // as primitive type str |
| // } |
| // ``` |
| Some(it) if matches!(it, PathResolution::Def(ModuleDef::BuiltinType(_))) => { |
| if let Some(mod_path) = hir_path.mod_path() |
| && let Some(ModuleDefId::ModuleId(id)) = |
| self.resolver.resolve_module_path_in_items(db, mod_path).take_types() |
| { |
| let parent_hir_name = parent_hir_path.segments().get(1).map(|it| it.name); |
| let module = crate::Module { id }; |
| if module |
| .scope(db, None) |
| .into_iter() |
| .any(|(name, _)| Some(&name) == parent_hir_name) |
| { |
| return Some((PathResolution::Def(ModuleDef::Module(module)), None)); |
| }; |
| } |
| Some((it, None)) |
| } |
| // FIXME: We do not show substitutions for parts of path, because this is really complex |
| // due to the interactions with associated items of `impl`s and associated items of associated |
| // types. |
| res => res.map(|it| (it, None)), |
| }; |
| } else if let Some(meta_path) = meta_path { |
| // Case where we are resolving the final path segment of a path in an attribute |
| // in this case we have to check for inert/builtin attributes and tools and prioritize |
| // resolution of attributes over other namespaces |
| if let Some(name_ref) = path.as_single_name_ref() { |
| let builtin = BuiltinAttr::builtin(&name_ref.text()); |
| if builtin.is_some() { |
| return builtin.map(|it| (PathResolution::BuiltinAttr(it), None)); |
| } |
| |
| if let Some(attr) = meta_path.parent_attr() { |
| let adt = |
| attr.syntax().ancestors().find_map(ast::Item::cast).and_then( |
| |it| match it { |
| ast::Item::Struct(it) => Some(ast::Adt::Struct(it)), |
| ast::Item::Enum(it) => Some(ast::Adt::Enum(it)), |
| ast::Item::Union(it) => Some(ast::Adt::Union(it)), |
| _ => None, |
| }, |
| ); |
| if let Some(adt) = adt { |
| let ast_id = self.file_id.ast_id_map(db).ast_id(&adt); |
| if let Some(helpers) = self |
| .resolver |
| .def_map() |
| .derive_helpers_in_scope(InFile::new(self.file_id, ast_id)) |
| { |
| // FIXME: Multiple derives can have the same helper |
| let name_ref = name_ref.as_name(); |
| for (macro_id, mut helpers) in |
| helpers.iter().chunk_by(|(_, macro_id, ..)| macro_id).into_iter() |
| { |
| if let Some(idx) = helpers.position(|(name, ..)| *name == name_ref) |
| { |
| return Some(( |
| PathResolution::DeriveHelper(DeriveHelper { |
| derive: *macro_id, |
| idx: idx as u32, |
| }), |
| None, |
| )); |
| } |
| } |
| } |
| } |
| } |
| } |
| return match resolve_hir_path_as_attr_macro(db, &self.resolver, &hir_path) { |
| Some(m) => Some((PathResolution::Def(ModuleDef::Macro(m)), None)), |
| // this labels any path that starts with a tool module as the tool itself, this is technically wrong |
| // but there is no benefit in differentiating these two cases for the time being |
| None => path |
| .first_segment() |
| .and_then(|it| it.name_ref()) |
| .and_then(|name_ref| { |
| ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text()) |
| .map(PathResolution::ToolModule) |
| }) |
| .map(|it| (it, None)), |
| }; |
| } |
| if parent().is_some_and(|it| ast::Visibility::can_cast(it.kind())) { |
| // No substitution because only modules can be inside visibilities, and those have no generics. |
| resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &store).map(|it| (it, None)) |
| } else { |
| // Probably a type, no need to show substitutions for those. |
| let res = resolve_hir_path_( |
| db, |
| &self.resolver, |
| self.infer_body, |
| &hir_path, |
| prefer_value_ns, |
| name_hygiene(db, InFile::new(self.file_id, path.syntax())), |
| Some(&store), |
| false, |
| ) |
| .any()?; |
| let subst = (|| { |
| let parent = parent()?; |
| let ty = if let Some(expr) = ast::Expr::cast(parent.clone()) { |
| let expr_id = self.expr_id(expr)?; |
| self.infer()?.type_of_expr_or_pat(expr_id)? |
| } else { |
| let pat = ast::Pat::cast(parent)?; |
| let pat_id = self.pat_id(&pat)?; |
| self.infer()?.expr_or_pat_ty(pat_id) |
| }; |
| let (subst, expected_resolution) = match ty.kind() { |
| TyKind::Adt(adt_def, subst) => { |
| let adt_id = adt_def.def_id(); |
| ( |
| GenericSubstitution::new(adt_id.into(), subst, self.type_owner), |
| PathResolution::Def(ModuleDef::Adt(adt_id.into())), |
| ) |
| } |
| TyKind::Alias(AliasTy { |
| kind: AliasTyKind::Projection { def_id }, |
| args, |
| .. |
| }) => { |
| let assoc_id = def_id.0; |
| ( |
| GenericSubstitution::new(assoc_id.into(), args, self.type_owner), |
| PathResolution::Def(ModuleDef::TypeAlias(assoc_id.into())), |
| ) |
| } |
| TyKind::FnDef(fn_id, subst) => { |
| let generic_def_id = match fn_id.0 { |
| CallableDefId::StructId(id) => id.into(), |
| CallableDefId::FunctionId(id) => id.into(), |
| CallableDefId::EnumVariantId(_) => return None, |
| }; |
| ( |
| GenericSubstitution::new(generic_def_id, subst, self.type_owner), |
| PathResolution::Def(ModuleDefId::from(fn_id.0).into()), |
| ) |
| } |
| _ => return None, |
| }; |
| if res != expected_resolution { |
| // The user will not understand where we're coming from. This can happen (I think) with type aliases. |
| return None; |
| } |
| Some(subst) |
| })(); |
| Some((res, subst)) |
| } |
| } |
| |
| pub(crate) fn resolve_hir_path_per_ns( |
| &self, |
| db: &'db dyn HirDatabase, |
| path: &ast::Path, |
| ) -> Option<PathResolutionPerNs<'db>> { |
| let mut collector = |
| ExprCollector::new(db, self.resolver.module(), self.file_id, LoweringMode::Ide); |
| let hir_path = |
| collector.lower_path(path.clone(), &mut ExprCollector::impl_trait_error_allocator)?; |
| let (store, _) = collector.store.finish(); |
| Some(resolve_hir_path_( |
| db, |
| &self.resolver, |
| self.infer_body, |
| &hir_path, |
| false, |
| name_hygiene(db, InFile::new(self.file_id, path.syntax())), |
| Some(&store), |
| true, |
| )) |
| } |
| |
| pub(crate) fn record_literal_missing_fields( |
| &self, |
| db: &'db dyn HirDatabase, |
| literal: &ast::RecordExpr, |
| ) -> Option<Vec<(Field, Type<'db>)>> { |
| let body = self.store()?; |
| let infer = self.infer()?; |
| |
| let expr_id = self.expr_id(literal.clone().into())?.as_expr()?; |
| let substs = infer.expr_ty(expr_id).as_adt()?.1; |
| let (variant, missing_fields) = |
| record_literal_missing_fields(db, infer, expr_id, &body[expr_id])?; |
| let res = self.missing_fields(db, substs, variant, missing_fields); |
| Some(res) |
| } |
| |
| pub(crate) fn record_literal_matched_fields( |
| &self, |
| db: &'db dyn HirDatabase, |
| literal: &ast::RecordExpr, |
| ) -> Option<Vec<(Field, Type<'db>)>> { |
| let body = self.store()?; |
| let infer = self.infer()?; |
| |
| let expr_id = self.expr_id(literal.clone().into())?.as_expr()?; |
| let substs = infer.expr_ty(expr_id).as_adt()?.1; |
| let (variant, matched_fields) = |
| record_literal_matched_fields(db, infer, expr_id, &body[expr_id])?; |
| |
| let res = self.missing_fields(db, substs, variant, matched_fields); |
| Some(res) |
| } |
| |
| pub(crate) fn record_pattern_missing_fields( |
| &self, |
| db: &'db dyn HirDatabase, |
| pattern: &ast::RecordPat, |
| ) -> Option<Vec<(Field, Type<'db>)>> { |
| let body = self.store()?; |
| let infer = self.infer()?; |
| |
| let pat_id = self.pat_id(&pattern.clone().into())?.as_pat()?; |
| let substs = infer.pat_ty(pat_id).as_adt()?.1; |
| |
| let (variant, missing_fields) = |
| record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?; |
| let res = self.missing_fields(db, substs, variant, missing_fields); |
| Some(res) |
| } |
| |
| pub(crate) fn record_pattern_matched_fields( |
| &self, |
| db: &'db dyn HirDatabase, |
| pattern: &ast::RecordPat, |
| ) -> Option<Vec<(Field, Type<'db>)>> { |
| let body = self.store()?; |
| let infer = self.infer()?; |
| |
| let pat_id = self.pat_id(&pattern.clone().into())?.as_pat()?; |
| let substs = infer.pat_ty(pat_id).as_adt()?.1; |
| |
| let (variant, matched_fields) = |
| record_pattern_matched_fields(db, infer, pat_id, &body[pat_id])?; |
| let res = self.missing_fields(db, substs, variant, matched_fields); |
| Some(res) |
| } |
| |
| fn missing_fields( |
| &self, |
| db: &'db dyn HirDatabase, |
| substs: GenericArgs<'db>, |
| variant: VariantId, |
| missing_fields: Vec<LocalFieldId>, |
| ) -> Vec<(Field, Type<'db>)> { |
| let interner = DbInterner::new_no_crate(db); |
| let field_types = db.field_types(variant); |
| |
| missing_fields |
| .into_iter() |
| .map(|local_id| { |
| let field = FieldId { parent: variant, local_id }; |
| let ty = field_types[local_id].ty().instantiate(interner, substs).skip_norm_wip(); |
| (field.into(), self.ty(ty)) |
| }) |
| .collect() |
| } |
| |
| pub(crate) fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantId> { |
| let infer = self.infer()?; |
| let expr_id = self.expr_id(record_lit.into())?; |
| infer.variant_resolution_for_expr_or_pat(expr_id) |
| } |
| |
| pub(crate) fn is_unsafe_macro_call_expr( |
| &self, |
| db: &'db dyn HirDatabase, |
| macro_expr: InFile<&ast::MacroExpr>, |
| ) -> bool { |
| if let Some((def, body, sm, Some(infer))) = self.def() |
| && let Some(expanded_expr) = sm.macro_expansion_expr(macro_expr) |
| { |
| let mut is_unsafe = false; |
| let mut walk_expr = |expr_id| { |
| unsafe_operations(db, infer, def, body, expr_id, &mut |_, inside_unsafe_block| { |
| is_unsafe |= inside_unsafe_block == InsideUnsafeBlock::No |
| }) |
| }; |
| match expanded_expr { |
| ExprOrPatId::ExprId(expanded_expr) => walk_expr(expanded_expr), |
| ExprOrPatId::PatId(expanded_pat) => body.walk_exprs_in_pat(expanded_pat, walk_expr), |
| } |
| return is_unsafe; |
| } |
| false |
| } |
| |
| /// Returns the range of the implicit template argument and its resolution at the given `offset` |
| pub(crate) fn resolve_offset_in_format_args( |
| &self, |
| db: &'db dyn HirDatabase, |
| format_args: InFile<&ast::FormatArgsExpr>, |
| offset: TextSize, |
| ) -> Option<(TextRange, Option<PathResolution<'db>>)> { |
| let (hygiene, implicits) = self.store_sm()?.implicit_format_args(format_args)?; |
| implicits.iter().find(|(range, _)| range.contains_inclusive(offset)).map(|(range, name)| { |
| ( |
| *range, |
| resolve_hir_value_path( |
| db, |
| &self.resolver, |
| self.resolver.expression_store_owner(), |
| self.infer_body, |
| &Path::from_known_path_with_no_generic(ModPath::from_segments( |
| PathKind::Plain, |
| Some(name.clone()), |
| )), |
| hygiene, |
| ) |
| .map(|(it, _)| it), |
| ) |
| }) |
| } |
| |
| pub(crate) fn resolve_offset_in_asm_template( |
| &self, |
| asm: InFile<&ast::AsmExpr>, |
| line: usize, |
| offset: TextSize, |
| ) -> Option<(ExpressionStoreOwnerId, (ExprId, TextRange, usize))> { |
| let (def, _, sm, _) = self.def()?; |
| let (expr, args) = sm.asm_template_args(asm)?; |
| Some(def).zip( |
| args.get(line)? |
| .iter() |
| .find(|(range, _)| range.contains_inclusive(offset)) |
| .map(|(range, idx)| (expr, *range, *idx)), |
| ) |
| } |
| |
| pub(crate) fn as_format_args_parts<'a>( |
| &'a self, |
| db: &'db dyn HirDatabase, |
| format_args: InFile<&ast::FormatArgsExpr>, |
| ) -> Option<impl Iterator<Item = (TextRange, Option<PathResolution<'db>>)> + 'a> { |
| let (hygiene, names) = self.store_sm()?.implicit_format_args(format_args)?; |
| Some(names.iter().map(move |(range, name)| { |
| ( |
| *range, |
| resolve_hir_value_path( |
| db, |
| &self.resolver, |
| self.resolver.expression_store_owner(), |
| self.infer_body, |
| &Path::from_known_path_with_no_generic(ModPath::from_segments( |
| PathKind::Plain, |
| Some(name.clone()), |
| )), |
| hygiene, |
| ) |
| .map(|(it, _)| it), |
| ) |
| })) |
| } |
| |
| pub(crate) fn as_asm_parts( |
| &self, |
| asm: InFile<&ast::AsmExpr>, |
| ) -> Option<(ExpressionStoreOwnerId, (ExprId, &[Vec<(TextRange, usize)>]))> { |
| let (def, _, sm, _) = self.def()?; |
| Some(def).zip(sm.asm_template_args(asm)) |
| } |
| |
| fn resolve_impl_method_or_trait_def( |
| &self, |
| db: &'db dyn HirDatabase, |
| func: FunctionId, |
| substs: GenericArgs<'db>, |
| ) -> Function { |
| self.resolve_impl_method_or_trait_def_with_subst(db, func, substs).0 |
| } |
| |
| fn resolve_impl_method_or_trait_def_with_subst( |
| &self, |
| db: &'db dyn HirDatabase, |
| func: FunctionId, |
| substs: GenericArgs<'db>, |
| ) -> (Function, GenericArgs<'db>) { |
| let owner = match self.resolver.generic_def() { |
| Some(it) => it, |
| None => return (func.into(), substs), |
| }; |
| let env = self.param_and(db.trait_environment(owner)); |
| let (func, args) = db.lookup_impl_method(env, func, substs); |
| match func { |
| Either::Left(func) => (func.into(), args), |
| Either::Right((impl_, method)) => { |
| (Function { id: AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ } }, args) |
| } |
| } |
| } |
| |
| fn resolve_impl_const_or_trait_def_with_subst( |
| &self, |
| db: &'db dyn HirDatabase, |
| const_id: ConstId, |
| subs: GenericArgs<'db>, |
| ) -> (ConstId, GenericArgs<'db>) { |
| let owner = match self.resolver.generic_def() { |
| Some(it) => it, |
| None => return (const_id, subs), |
| }; |
| let env = self.param_and(db.trait_environment(owner)); |
| let interner = DbInterner::new_with(db, env.krate); |
| let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis); |
| method_resolution::lookup_impl_const(&infcx, env.param_env, const_id, subs) |
| } |
| |
| fn lang_items<'a>(&self, db: &'a dyn HirDatabase) -> &'a LangItems { |
| hir_def::lang_item::lang_items(db, self.resolver.krate()) |
| } |
| |
| fn ty_of_expr(&self, expr: ast::Expr) -> Option<Ty<'db>> { |
| self.infer()?.type_of_expr_or_pat(self.expr_id(expr)?) |
| } |
| } |
| |
| // Note: the `ExprId` here does not need to be accurate, what's important is that it points at the same |
| // inference root. |
| fn scope_for( |
| db: &dyn HirDatabase, |
| scopes: &ExprScopes, |
| source_map: &ExpressionStoreSourceMap, |
| node: InFile<&SyntaxNode>, |
| ) -> Option<(ScopeId, ExprId)> { |
| node.ancestors_with_macros(db) |
| .take_while(|it| { |
| let kind = it.kind(); |
| !ast::Item::can_cast(kind) |
| || ast::MacroCall::can_cast(kind) |
| || ast::Use::can_cast(kind) |
| || ast::AsmExpr::can_cast(kind) |
| }) |
| .filter_map(|it| it.map(ast::Expr::cast).transpose()) |
| .filter_map(|it| source_map.node_expr(it.as_ref())?.as_expr()) |
| .find_map(|expr| scopes.scope_for(expr).map(|scope| (scope, expr))) |
| } |
| |
| fn scope_for_offset( |
| db: &dyn HirDatabase, |
| scopes: &ExprScopes, |
| source_map: &ExpressionStoreSourceMap, |
| from_file: HirFileId, |
| offset: TextSize, |
| ) -> Option<(ScopeId, ExprId)> { |
| scopes |
| .scope_by_expr() |
| .iter() |
| .filter_map(|(id, scope)| { |
| let InFile { file_id, value } = source_map.expr_syntax(id).ok()?; |
| if from_file == file_id { |
| return Some((value.text_range(), scope, id)); |
| } |
| |
| // FIXME handle attribute expansion |
| let source = iter::successors(file_id.macro_file().map(|it| it.call_node(db)), |it| { |
| Some(it.file_id.macro_file()?.call_node(db)) |
| }) |
| .find(|it| it.file_id == from_file) |
| .filter(|it| it.kind() == SyntaxKind::MACRO_CALL)?; |
| Some((source.text_range(), scope, id)) |
| }) |
| .filter(|(expr_range, _scope, _expr)| { |
| expr_range.start() <= offset && offset <= expr_range.end() |
| }) |
| // find containing scope |
| .min_by_key(|(expr_range, _scope, _expr)| expr_range.len()) |
| .map(|(expr_range, scope, expr)| { |
| adjust(db, scopes, source_map, expr_range, from_file, offset).unwrap_or((*scope, expr)) |
| }) |
| } |
| |
| // XXX: during completion, cursor might be outside of any particular |
| // expression. Try to figure out the correct scope... |
| fn adjust( |
| db: &dyn HirDatabase, |
| scopes: &ExprScopes, |
| source_map: &ExpressionStoreSourceMap, |
| expr_range: TextRange, |
| from_file: HirFileId, |
| offset: TextSize, |
| ) -> Option<(ScopeId, ExprId)> { |
| let child_scopes = scopes |
| .scope_by_expr() |
| .iter() |
| .filter_map(|(id, scope)| { |
| let source = source_map.expr_syntax(id).ok()?; |
| // FIXME: correctly handle macro expansion |
| if source.file_id != from_file { |
| return None; |
| } |
| let root = source.file_syntax(db); |
| let node = source.value.to_node(&root); |
| Some((node.syntax().text_range(), scope, id)) |
| }) |
| .filter(|&(range, _, _)| { |
| range.start() <= offset && expr_range.contains_range(range) && range != expr_range |
| }); |
| |
| child_scopes |
| .max_by(|&(r1, _, _), &(r2, _, _)| { |
| if r1.contains_range(r2) { |
| std::cmp::Ordering::Greater |
| } else if r2.contains_range(r1) { |
| std::cmp::Ordering::Less |
| } else { |
| r1.start().cmp(&r2.start()) |
| } |
| }) |
| .map(|(_ptr, scope, expr)| (*scope, expr)) |
| } |
| |
| #[inline] |
| pub(crate) fn resolve_hir_path<'db>( |
| db: &'db dyn HirDatabase, |
| resolver: &Resolver<'db>, |
| infer_body: Option<InferBodyId<'db>>, |
| path: &Path, |
| hygiene: HygieneId, |
| store: Option<&ExpressionStore>, |
| ) -> Option<PathResolution<'db>> { |
| resolve_hir_path_(db, resolver, infer_body, path, false, hygiene, store, false).any() |
| } |
| |
| #[inline] |
| pub(crate) fn resolve_hir_path_as_attr_macro( |
| db: &dyn HirDatabase, |
| resolver: &Resolver<'_>, |
| path: &Path, |
| ) -> Option<Macro> { |
| resolver |
| .resolve_path_as_macro(db, path.mod_path()?, Some(MacroSubNs::Attr)) |
| .map(|it| it.def) |
| .map(Into::into) |
| } |
| |
| fn resolve_hir_path_<'db>( |
| db: &'db dyn HirDatabase, |
| resolver: &Resolver<'db>, |
| infer_body: Option<InferBodyId<'db>>, |
| path: &Path, |
| prefer_value_ns: bool, |
| hygiene: HygieneId, |
| store: Option<&ExpressionStore>, |
| resolve_per_ns: bool, |
| ) -> PathResolutionPerNs<'db> { |
| let types = || { |
| let (ty, unresolved, ty_is_visible) = match path.type_anchor() { |
| Some(type_ref) => resolver.generic_def().and_then(|def| { |
| let generics = OnceCell::new(); |
| let (_, res) = TyLoweringContext::new( |
| db, |
| resolver, |
| store?, |
| def.into(), |
| def, |
| &generics, |
| LifetimeElisionKind::Infer, |
| LifetimeLoweringMode::LateParam, |
| ) |
| .lower_ty_ext(type_ref); |
| res.map(|ty_ns| (ty_ns, path.segments().first(), Visibility::Public)) |
| }), |
| None => { |
| let (ty, remaining_idx, _, _, vis) = |
| resolver.resolve_path_in_type_ns_with_prefix_info(db, path)?; |
| match remaining_idx { |
| Some(remaining_idx) => { |
| if remaining_idx + 1 == path.segments().len() { |
| Some((ty, path.segments().last(), vis)) |
| } else { |
| None |
| } |
| } |
| None => Some((ty, None, vis)), |
| } |
| } |
| }?; |
| |
| // If we are in a TypeNs for a Trait, and we have an unresolved name, try to resolve it as a type |
| // within the trait's associated types. |
| if let (Some(unresolved), &TypeNs::TraitId(trait_id)) = (&unresolved, &ty) |
| && let Some(type_alias_id) = |
| trait_id.trait_items(db).associated_type_by_name(unresolved.name) |
| { |
| return Some(( |
| PathResolution::Def(ModuleDefId::from(type_alias_id).into()), |
| ty_is_visible, |
| )); |
| } |
| |
| let res = match ty { |
| TypeNs::SelfType(it) => PathResolution::SelfType(it.into()), |
| TypeNs::GenericParam(id) => PathResolution::TypeParam(id.into()), |
| TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => { |
| PathResolution::Def(Adt::from(it).into()) |
| } |
| TypeNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), |
| TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()), |
| TypeNs::BuiltinType(it) => PathResolution::Def(BuiltinType::from(it).into()), |
| TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), |
| TypeNs::ModuleId(it) => PathResolution::Def(ModuleDef::Module(it.into())), |
| }; |
| match unresolved { |
| Some(unresolved) => resolver |
| .generic_def() |
| .and_then(|def| { |
| hir_ty::associated_type_shorthand_candidates( |
| db, |
| def, |
| res.in_type_ns()?, |
| |name, _| name == unresolved.name, |
| ) |
| }) |
| .map(TypeAlias::from) |
| .map(Into::into) |
| .map(|def| (PathResolution::Def(def), ty_is_visible)), |
| None => Some((res, ty_is_visible)), |
| } |
| }; |
| |
| let body_owner = resolver.expression_store_owner(); |
| let values = || resolve_hir_value_path(db, resolver, body_owner, infer_body, path, hygiene); |
| |
| let items = || { |
| resolver |
| .resolve_module_path_in_items(db, path.mod_path()?) |
| .take_types_full() |
| .map(|it| (PathResolution::Def(it.def.into()), it.vis)) |
| }; |
| |
| let macros = || { |
| resolver |
| .resolve_path_as_macro(db, path.mod_path()?, None) |
| .map(|res| (PathResolution::Def(ModuleDef::Macro(res.def.into())), res.vis)) |
| }; |
| |
| let mut types_ns: Option<Option<_>> = None; |
| let mut values_ns: Option<Option<_>> = None; |
| |
| let mut types_is_visible: Option<bool> = None; |
| let mut values_is_visible: Option<bool> = None; |
| |
| if !resolve_per_ns { |
| if prefer_value_ns { |
| values_ns = Some(values().inspect(|(_, vis)| { |
| values_is_visible = Some(resolver.is_visible(db, *vis)); |
| })); |
| |
| if let Some(Some((res, _))) = values_ns |
| && values_is_visible.unwrap_or_default() |
| { |
| return PathResolutionPerNs::new(None, Some(res), None); |
| } |
| } else { |
| types_ns = Some(types().or_else(items).inspect(|(_, vis)| { |
| types_is_visible = Some(resolver.is_visible(db, *vis)); |
| })); |
| |
| if let Some(Some((res, _))) = types_ns |
| && types_is_visible.unwrap_or_default() |
| { |
| return PathResolutionPerNs::new(Some(res), None, None); |
| } |
| } |
| } |
| |
| let mut macros_is_visible = false; |
| |
| let mut types = types_ns.unwrap_or_else(|| types().or_else(items)).map(|(res, vis)| { |
| types_is_visible = Some(types_is_visible.unwrap_or_else(|| resolver.is_visible(db, vis))); |
| res |
| }); |
| let mut values = values_ns.unwrap_or_else(values).map(|(res, vis)| { |
| values_is_visible = Some(values_is_visible.unwrap_or_else(|| resolver.is_visible(db, vis))); |
| res |
| }); |
| let mut macros = macros().map(|(res, vis)| { |
| macros_is_visible = resolver.is_visible(db, vis); |
| res |
| }); |
| |
| let types_is_visible = types_is_visible.unwrap_or_default(); |
| let values_is_visible = values_is_visible.unwrap_or_default(); |
| |
| // If there is a visible resolution and an invisible one, we only want to include the visible one. But if all are |
| // invisible, we want to include them all. |
| if types_is_visible || values_is_visible || macros_is_visible { |
| if !types_is_visible { |
| types = None; |
| } |
| if !values_is_visible { |
| values = None; |
| } |
| if !macros_is_visible { |
| macros = None; |
| } |
| } |
| |
| PathResolutionPerNs { type_ns: types, value_ns: values, macro_ns: macros } |
| } |
| |
| fn resolve_hir_value_path<'db>( |
| db: &'db dyn HirDatabase, |
| resolver: &Resolver<'db>, |
| store_owner: Option<ExpressionStoreOwnerId>, |
| infer_body: Option<InferBodyId<'db>>, |
| path: &Path, |
| hygiene: HygieneId, |
| ) -> Option<(PathResolution<'db>, Visibility)> { |
| resolver.resolve_path_in_value_ns_with_prefix_info(db, path, hygiene).and_then( |
| |(val, _, vis)| { |
| let ResolveValueResult::ValueNs(val) = val else { return None }; |
| let res = match val { |
| ValueNs::LocalBinding(binding_id) => { |
| let var = Local { parent: store_owner?, parent_infer: infer_body?, binding_id }; |
| PathResolution::Local(var) |
| } |
| ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()), |
| ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()), |
| ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()), |
| ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()), |
| ValueNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), |
| ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()), |
| ValueNs::GenericParam(id) => PathResolution::ConstParam(id.into()), |
| }; |
| Some((res, vis)) |
| }, |
| ) |
| } |
| |
| /// Resolves a path where we know it is a qualifier of another path. |
| /// |
| /// For example, if we have: |
| /// ``` |
| /// mod my { |
| /// pub mod foo { |
| /// struct Bar; |
| /// } |
| /// |
| /// pub fn foo() {} |
| /// } |
| /// ``` |
| /// then we know that `foo` in `my::foo::Bar` refers to the module, not the function. |
| fn resolve_hir_path_qualifier<'db>( |
| db: &'db dyn HirDatabase, |
| resolver: &Resolver<'db>, |
| path: &Path, |
| store: &ExpressionStore, |
| ) -> Option<PathResolution<'db>> { |
| (|| { |
| let (ty, unresolved) = match path.type_anchor() { |
| Some(type_ref) => resolver.generic_def().and_then(|def| { |
| let generics = OnceCell::new(); |
| let (_, res) = TyLoweringContext::new( |
| db, |
| resolver, |
| store, |
| def.into(), |
| def, |
| &generics, |
| LifetimeElisionKind::Infer, |
| LifetimeLoweringMode::LateParam, |
| ) |
| .lower_ty_ext(type_ref); |
| res.map(|ty_ns| (ty_ns, path.segments().first())) |
| }), |
| None => { |
| let (ty, remaining_idx, _) = resolver.resolve_path_in_type_ns(db, path)?; |
| match remaining_idx { |
| Some(remaining_idx) => { |
| if remaining_idx + 1 == path.segments().len() { |
| Some((ty, path.segments().last())) |
| } else { |
| None |
| } |
| } |
| None => Some((ty, None)), |
| } |
| } |
| }?; |
| |
| // If we are in a TypeNs for a Trait, and we have an unresolved name, try to resolve it as a type |
| // within the trait's associated types. |
| if let (Some(unresolved), &TypeNs::TraitId(trait_id)) = (&unresolved, &ty) |
| && let Some(type_alias_id) = |
| trait_id.trait_items(db).associated_type_by_name(unresolved.name) |
| { |
| return Some(PathResolution::Def(ModuleDefId::from(type_alias_id).into())); |
| } |
| |
| let res = match ty { |
| TypeNs::SelfType(it) => PathResolution::SelfType(it.into()), |
| TypeNs::GenericParam(id) => PathResolution::TypeParam(id.into()), |
| TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => { |
| PathResolution::Def(Adt::from(it).into()) |
| } |
| TypeNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), |
| TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()), |
| TypeNs::BuiltinType(it) => PathResolution::Def(BuiltinType::from(it).into()), |
| TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), |
| TypeNs::ModuleId(it) => PathResolution::Def(ModuleDef::Module(it.into())), |
| }; |
| match unresolved { |
| Some(unresolved) => resolver |
| .generic_def() |
| .and_then(|def| { |
| hir_ty::associated_type_shorthand_candidates( |
| db, |
| def, |
| res.in_type_ns()?, |
| |name, _| name == unresolved.name, |
| ) |
| }) |
| .map(TypeAlias::from) |
| .map(Into::into) |
| .map(PathResolution::Def), |
| None => Some(res), |
| } |
| })() |
| .or_else(|| { |
| resolver |
| .resolve_module_path_in_items(db, path.mod_path()?) |
| .take_types() |
| .map(|it| PathResolution::Def(it.into())) |
| }) |
| } |
| |
| pub(crate) fn name_hygiene(db: &dyn HirDatabase, name: InFile<&SyntaxNode>) -> HygieneId { |
| let Some(macro_file) = name.file_id.macro_file() else { |
| return HygieneId::ROOT; |
| }; |
| let span_map = macro_file.expansion_span_map(db); |
| let ctx = span_map.span_at(name.value.text_range().start()).ctx; |
| HygieneId::new(ctx.opaque_and_semiopaque(db)) |
| } |
| |
| fn record_literal_matched_fields( |
| db: &dyn HirDatabase, |
| infer: &InferenceResult<'_>, |
| id: ExprId, |
| expr: &Expr, |
| ) -> Option<(VariantId, Vec<LocalFieldId>)> { |
| let (fields, _spread) = match expr { |
| Expr::RecordLit { fields, spread, .. } => (fields, spread), |
| _ => return None, |
| }; |
| |
| let variant_def = infer.variant_resolution_for_expr(id)?; |
| if let VariantId::UnionId(_) = variant_def { |
| return None; |
| } |
| |
| let variant_data = variant_def.fields(db); |
| |
| let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); |
| // suggest fields if: |
| // - not in code |
| let matched_fields: Vec<LocalFieldId> = variant_data |
| .fields() |
| .iter() |
| .filter_map(|(f, d)| (!specified_fields.contains(&d.name)).then_some(f)) |
| .collect(); |
| if matched_fields.is_empty() { |
| return None; |
| } |
| Some((variant_def, matched_fields)) |
| } |
| |
| fn record_pattern_matched_fields( |
| db: &dyn HirDatabase, |
| infer: &InferenceResult<'_>, |
| id: PatId, |
| pat: &Pat, |
| ) -> Option<(VariantId, Vec<LocalFieldId>)> { |
| let (fields, _ellipsis) = match pat { |
| Pat::Record { path: _, args, ellipsis } => (args, *ellipsis), |
| _ => return None, |
| }; |
| |
| let variant_def = infer.variant_resolution_for_pat(id)?; |
| if let VariantId::UnionId(_) = variant_def { |
| return None; |
| } |
| |
| let variant_data = variant_def.fields(db); |
| |
| let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); |
| // suggest fields if: |
| // - not in code |
| let matched_fields: Vec<LocalFieldId> = variant_data |
| .fields() |
| .iter() |
| .filter_map(|(f, d)| if !specified_fields.contains(&d.name) { Some(f) } else { None }) |
| .collect(); |
| if matched_fields.is_empty() { |
| return None; |
| } |
| Some((variant_def, matched_fields)) |
| } |