//! 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::sync::Arc; use either::Either; use hir_def::{ body::{ scope::{ExprScopes, ScopeId}, BodySourceMap, }, expr::{ExprId, PatId}, resolver::{self, resolver_for_scope, Resolver, TypeNs, ValueNs}, AsMacroCall, DefWithBodyId, TraitId, }; use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile, MacroCallId}; use hir_ty::{InEnvironment, InferenceResult, TraitEnvironment}; use ra_syntax::{ ast::{self, AstNode}, AstPtr, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextUnit, }; use rustc_hash::FxHashSet; use crate::{ db::HirDatabase, Adt, Const, DefWithBody, EnumVariant, Function, Local, MacroDef, Name, Path, ScopeDef, Static, Struct, Trait, Type, TypeAlias, TypeParam, }; /// `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 struct SourceAnalyzer { file_id: HirFileId, resolver: Resolver, body_owner: Option, body_source_map: Option>, infer: Option>, scopes: Option>, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum PathResolution { /// An item Def(crate::ModuleDef), /// A local binding (only value namespace) Local(Local), /// A generic parameter TypeParam(TypeParam), SelfType(crate::ImplBlock), Macro(MacroDef), AssocItem(crate::AssocItem), } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ScopeEntryWithSyntax { pub(crate) name: Name, pub(crate) ptr: Either, AstPtr>, } impl ScopeEntryWithSyntax { pub fn name(&self) -> &Name { &self.name } pub fn ptr(&self) -> Either, AstPtr> { self.ptr } } #[derive(Debug)] pub struct ReferenceDescriptor { pub range: TextRange, pub name: String, } #[derive(Debug)] pub struct Expansion { macro_call_id: MacroCallId, } impl Expansion { pub fn map_token_down( &self, db: &impl HirDatabase, token: InFile<&SyntaxToken>, ) -> Option> { let exp_info = self.file_id().expansion_info(db)?; exp_info.map_token_down(token) } pub fn file_id(&self) -> HirFileId { self.macro_call_id.as_file() } } impl SourceAnalyzer { pub fn new( db: &impl HirDatabase, node: InFile<&SyntaxNode>, offset: Option, ) -> SourceAnalyzer { crate::source_binder::SourceBinder::new(db).analyze(node, offset) } pub(crate) fn new_for_body( db: &impl HirDatabase, def: DefWithBodyId, node: InFile<&SyntaxNode>, offset: Option, ) -> SourceAnalyzer { let (_body, source_map) = db.body_with_source_map(def); let scopes = db.expr_scopes(def); let scope = match offset { None => scope_for(&scopes, &source_map, node), Some(offset) => scope_for_offset(&scopes, &source_map, node.with_value(offset)), }; let resolver = resolver_for_scope(db, def, scope); SourceAnalyzer { resolver, body_owner: Some(def.into()), body_source_map: Some(source_map), infer: Some(db.infer(def)), scopes: Some(scopes), file_id: node.file_id, } } pub(crate) fn new_for_resolver( resolver: Resolver, node: InFile<&SyntaxNode>, ) -> SourceAnalyzer { SourceAnalyzer { resolver, body_owner: None, body_source_map: None, infer: None, scopes: None, file_id: node.file_id, } } pub fn module(&self) -> Option { Some(crate::code_model::Module { id: self.resolver.module()? }) } fn expr_id(&self, expr: &ast::Expr) -> Option { let src = InFile { file_id: self.file_id, value: expr }; self.body_source_map.as_ref()?.node_expr(src) } fn pat_id(&self, pat: &ast::Pat) -> Option { let src = InFile { file_id: self.file_id, value: pat }; self.body_source_map.as_ref()?.node_pat(src) } fn expand_expr( &self, db: &impl HirDatabase, expr: InFile<&ast::Expr>, ) -> Option> { let macro_call = ast::MacroCall::cast(expr.value.syntax().clone())?; let macro_file = self.body_source_map.as_ref()?.node_macro_file(expr.with_value(¯o_call))?; let expanded = db.parse_or_expand(macro_file)?; let kind = expanded.kind(); let expr = InFile::new(macro_file, ast::Expr::cast(expanded)?); if ast::MacroCall::can_cast(kind) { self.expand_expr(db, expr.as_ref()) } else { Some(expr) } } fn trait_env(&self, db: &impl HirDatabase) -> Arc { TraitEnvironment::lower(db, &self.resolver) } pub fn type_of(&self, db: &impl HirDatabase, expr: &ast::Expr) -> Option { let expr_id = if let Some(expr) = self.expand_expr(db, InFile::new(self.file_id, expr)) { self.body_source_map.as_ref()?.node_expr(expr.as_ref())? } else { self.expr_id(expr)? }; let ty = self.infer.as_ref()?[expr_id].clone(); let environment = self.trait_env(db); Some(Type { krate: self.resolver.krate()?, ty: InEnvironment { value: ty, environment } }) } pub fn type_of_pat(&self, db: &impl HirDatabase, pat: &ast::Pat) -> Option { let pat_id = self.pat_id(pat)?; let ty = self.infer.as_ref()?[pat_id].clone(); let environment = self.trait_env(db); Some(Type { krate: self.resolver.krate()?, ty: InEnvironment { value: ty, environment } }) } pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option { let expr_id = self.expr_id(&call.clone().into())?; self.infer.as_ref()?.method_resolution(expr_id).map(Function::from) } pub fn resolve_field(&self, field: &ast::FieldExpr) -> Option { let expr_id = self.expr_id(&field.clone().into())?; self.infer.as_ref()?.field_resolution(expr_id).map(|it| it.into()) } pub fn resolve_record_field(&self, field: &ast::RecordField) -> Option { let expr_id = match field.expr() { Some(it) => self.expr_id(&it)?, None => { let src = InFile { file_id: self.file_id, value: field }; self.body_source_map.as_ref()?.field_init_shorthand_expr(src)? } }; self.infer.as_ref()?.record_field_resolution(expr_id).map(|it| it.into()) } pub fn resolve_record_literal(&self, record_lit: &ast::RecordLit) -> Option { let expr_id = self.expr_id(&record_lit.clone().into())?; self.infer.as_ref()?.variant_resolution_for_expr(expr_id).map(|it| it.into()) } pub fn resolve_record_pattern(&self, record_pat: &ast::RecordPat) -> Option { let pat_id = self.pat_id(&record_pat.clone().into())?; self.infer.as_ref()?.variant_resolution_for_pat(pat_id).map(|it| it.into()) } pub fn resolve_macro_call( &self, db: &impl HirDatabase, macro_call: InFile<&ast::MacroCall>, ) -> Option { let hygiene = Hygiene::new(db, macro_call.file_id); let path = macro_call.value.path().and_then(|ast| Path::from_src(ast, &hygiene))?; self.resolver.resolve_path_as_macro(db, path.mod_path()).map(|it| it.into()) } pub fn resolve_hir_path( &self, db: &impl HirDatabase, path: &crate::Path, ) -> Option { let types = self.resolver.resolve_path_in_type_ns_fully(db, path.mod_path()).map(|ty| match ty { TypeNs::SelfType(it) => PathResolution::SelfType(it.into()), TypeNs::GenericParam(id) => PathResolution::TypeParam(TypeParam { id }), 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(it.into()), TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), }); let values = self.resolver.resolve_path_in_value_ns_fully(db, path.mod_path()).and_then(|val| { let res = match val { ValueNs::LocalBinding(pat_id) => { let var = Local { parent: self.body_owner?, pat_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()), }; Some(res) }); let items = self .resolver .resolve_module_path_in_items(db, path.mod_path()) .take_types() .map(|it| PathResolution::Def(it.into())); types.or(values).or(items).or_else(|| { self.resolver .resolve_path_as_macro(db, path.mod_path()) .map(|def| PathResolution::Macro(def.into())) }) } pub fn resolve_path(&self, db: &impl HirDatabase, path: &ast::Path) -> Option { if let Some(path_expr) = path.syntax().parent().and_then(ast::PathExpr::cast) { let expr_id = self.expr_id(&path_expr.into())?; if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) { return Some(PathResolution::AssocItem(assoc.into())); } } if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) { let pat_id = self.pat_id(&path_pat.into())?; if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) { return Some(PathResolution::AssocItem(assoc.into())); } } // This must be a normal source file rather than macro file. let hir_path = crate::Path::from_ast(path.clone())?; self.resolve_hir_path(db, &hir_path) } fn resolve_local_name(&self, name_ref: &ast::NameRef) -> Option { let name = name_ref.as_name(); let source_map = self.body_source_map.as_ref()?; let scopes = self.scopes.as_ref()?; let scope = scope_for(scopes, source_map, InFile::new(self.file_id, name_ref.syntax()))?; let entry = scopes.resolve_name_in_scope(scope, &name)?; Some(ScopeEntryWithSyntax { name: entry.name().clone(), ptr: source_map.pat_syntax(entry.pat())?.value, }) } pub fn process_all_names(&self, db: &impl HirDatabase, f: &mut dyn FnMut(Name, ScopeDef)) { self.resolver.process_all_names(db, &mut |name, def| { let def = match def { resolver::ScopeDef::PerNs(it) => it.into(), resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()), resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()), resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(TypeParam { id }), resolver::ScopeDef::Local(pat_id) => { let parent = self.resolver.body_owner().unwrap().into(); ScopeDef::Local(Local { parent, pat_id }) } }; f(name, def) }) } // FIXME: we only use this in `inline_local_variable` assist, ideally, we // should switch to general reference search infra there. pub fn find_all_refs(&self, pat: &ast::BindPat) -> Vec { let fn_def = pat.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); let ptr = Either::Left(AstPtr::new(&ast::Pat::from(pat.clone()))); fn_def .syntax() .descendants() .filter_map(ast::NameRef::cast) .filter(|name_ref| match self.resolve_local_name(&name_ref) { None => false, Some(entry) => entry.ptr() == ptr, }) .map(|name_ref| ReferenceDescriptor { name: name_ref.text().to_string(), range: name_ref.syntax().text_range(), }) .collect() } /// Note: `FxHashSet` should be treated as an opaque type, passed into `Type pub fn traits_in_scope(&self, db: &impl HirDatabase) -> FxHashSet { self.resolver.traits_in_scope(db) } pub fn expand( &self, db: &impl HirDatabase, macro_call: InFile<&ast::MacroCall>, ) -> Option { let macro_call_id = macro_call.as_call_id(db, |path| self.resolver.resolve_path_as_macro(db, &path))?; Some(Expansion { macro_call_id }) } } fn scope_for( scopes: &ExprScopes, source_map: &BodySourceMap, node: InFile<&SyntaxNode>, ) -> Option { node.value .ancestors() .filter_map(ast::Expr::cast) .filter_map(|it| source_map.node_expr(InFile::new(node.file_id, &it))) .find_map(|it| scopes.scope_for(it)) } fn scope_for_offset( scopes: &ExprScopes, source_map: &BodySourceMap, offset: InFile, ) -> Option { scopes .scope_by_expr() .iter() .filter_map(|(id, scope)| { let source = source_map.expr_syntax(*id)?; // FIXME: correctly handle macro expansion if source.file_id != offset.file_id { return None; } let syntax_node_ptr = source.value.either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); Some((syntax_node_ptr, scope)) }) // find containing scope .min_by_key(|(ptr, _scope)| { ( !(ptr.range().start() <= offset.value && offset.value <= ptr.range().end()), ptr.range().len(), ) }) .map(|(ptr, scope)| { adjust(scopes, source_map, ptr, offset.file_id, offset.value).unwrap_or(*scope) }) } // XXX: during completion, cursor might be outside of any particular // expression. Try to figure out the correct scope... fn adjust( scopes: &ExprScopes, source_map: &BodySourceMap, ptr: SyntaxNodePtr, file_id: HirFileId, offset: TextUnit, ) -> Option { let r = ptr.range(); let child_scopes = scopes .scope_by_expr() .iter() .filter_map(|(id, scope)| { let source = source_map.expr_syntax(*id)?; // FIXME: correctly handle macro expansion if source.file_id != file_id { return None; } let syntax_node_ptr = source.value.either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); Some((syntax_node_ptr, scope)) }) .map(|(ptr, scope)| (ptr.range(), scope)) .filter(|(range, _)| range.start() <= offset && range.is_subrange(&r) && *range != r); child_scopes .max_by(|(r1, _), (r2, _)| { if r2.is_subrange(&r1) { std::cmp::Ordering::Greater } else if r1.is_subrange(&r2) { std::cmp::Ordering::Less } else { r1.start().cmp(&r2.start()) } }) .map(|(_ptr, scope)| *scope) }