From ae71a631fd657368e8593feb5e025d23147afe60 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 13 Aug 2020 16:36:55 +0200 Subject: Rename ra_hir -> hir --- crates/hir/src/source_analyzer.rs | 534 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 crates/hir/src/source_analyzer.rs (limited to 'crates/hir/src/source_analyzer.rs') diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs new file mode 100644 index 000000000..8750584f9 --- /dev/null +++ b/crates/hir/src/source_analyzer.rs @@ -0,0 +1,534 @@ +//! 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::{iter::once, sync::Arc}; + +use hir_def::{ + body::{ + scope::{ExprScopes, ScopeId}, + Body, BodySourceMap, + }, + expr::{ExprId, Pat, PatId}, + resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, + AsMacroCall, DefWithBodyId, FieldId, FunctionId, LocalFieldId, VariantId, +}; +use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile}; +use hir_ty::{ + diagnostics::{record_literal_missing_fields, record_pattern_missing_fields}, + InferenceResult, Substs, Ty, +}; +use syntax::{ + ast::{self, AstNode}, + SyntaxNode, TextRange, TextSize, +}; + +use crate::{ + db::HirDatabase, semantics::PathResolution, Adt, Const, EnumVariant, Field, Function, Local, + MacroDef, ModPath, ModuleDef, Path, PathKind, Static, Struct, Trait, Type, TypeAlias, + TypeParam, +}; +use base_db::CrateId; + +/// `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 { + file_id: HirFileId, + pub(crate) resolver: Resolver, + body: Option>, + body_source_map: Option>, + infer: Option>, + scopes: Option>, +} + +impl SourceAnalyzer { + pub(crate) fn new_for_body( + db: &dyn 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(db, &scopes, &source_map, node.with_value(offset)), + }; + let resolver = resolver_for_scope(db.upcast(), def, scope); + SourceAnalyzer { + resolver, + body: Some(body), + 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: None, + body_source_map: None, + infer: None, + scopes: None, + file_id: node.file_id, + } + } + + fn expr_id(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option { + let src = match expr { + ast::Expr::MacroCall(call) => { + self.expand_expr(db, InFile::new(self.file_id, call.clone()))? + } + _ => InFile::new(self.file_id, expr.clone()), + }; + let sm = self.body_source_map.as_ref()?; + sm.node_expr(src.as_ref()) + } + + fn pat_id(&self, pat: &ast::Pat) -> Option { + // FIXME: macros, see `expr_id` + let src = InFile { file_id: self.file_id, value: pat }; + self.body_source_map.as_ref()?.node_pat(src) + } + + fn expand_expr( + &self, + db: &dyn HirDatabase, + expr: InFile, + ) -> Option> { + let macro_file = self.body_source_map.as_ref()?.node_macro_file(expr.as_ref())?; + let expanded = db.parse_or_expand(macro_file)?; + + let res = match ast::MacroCall::cast(expanded.clone()) { + Some(call) => self.expand_expr(db, InFile::new(macro_file, call))?, + _ => InFile::new(macro_file, ast::Expr::cast(expanded)?), + }; + Some(res) + } + + pub(crate) fn type_of_expr(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option { + let expr_id = self.expr_id(db, expr)?; + let ty = self.infer.as_ref()?[expr_id].clone(); + Type::new_with_resolver(db, &self.resolver, ty) + } + + pub(crate) fn type_of_pat(&self, db: &dyn HirDatabase, pat: &ast::Pat) -> Option { + let pat_id = self.pat_id(pat)?; + let ty = self.infer.as_ref()?[pat_id].clone(); + Type::new_with_resolver(db, &self.resolver, ty) + } + + pub(crate) fn type_of_self( + &self, + db: &dyn HirDatabase, + param: &ast::SelfParam, + ) -> Option { + let src = InFile { file_id: self.file_id, value: param }; + let pat_id = self.body_source_map.as_ref()?.node_self_param(src)?; + let ty = self.infer.as_ref()?[pat_id].clone(); + Type::new_with_resolver(db, &self.resolver, ty) + } + + pub(crate) fn resolve_method_call( + &self, + db: &dyn HirDatabase, + call: &ast::MethodCallExpr, + ) -> Option { + let expr_id = self.expr_id(db, &call.clone().into())?; + self.infer.as_ref()?.method_resolution(expr_id) + } + + pub(crate) fn resolve_field( + &self, + db: &dyn HirDatabase, + field: &ast::FieldExpr, + ) -> Option { + let expr_id = self.expr_id(db, &field.clone().into())?; + self.infer.as_ref()?.field_resolution(expr_id).map(|it| it.into()) + } + + pub(crate) fn resolve_record_field( + &self, + db: &dyn HirDatabase, + field: &ast::RecordExprField, + ) -> Option<(Field, Option)> { + let expr = field.expr()?; + let expr_id = self.expr_id(db, &expr)?; + let local = if field.name_ref().is_some() { + None + } else { + let local_name = field.field_name()?.as_name(); + let path = ModPath::from_segments(PathKind::Plain, once(local_name)); + match self.resolver.resolve_path_in_value_ns_fully(db.upcast(), &path) { + Some(ValueNs::LocalBinding(pat_id)) => { + Some(Local { pat_id, parent: self.resolver.body_owner()? }) + } + _ => None, + } + }; + let struct_field = self.infer.as_ref()?.record_field_resolution(expr_id)?; + Some((struct_field.into(), local)) + } + + pub(crate) fn resolve_record_field_pat( + &self, + _db: &dyn HirDatabase, + field: &ast::RecordPatField, + ) -> Option { + let pat_id = self.pat_id(&field.pat()?)?; + let struct_field = self.infer.as_ref()?.record_field_pat_resolution(pat_id)?; + Some(struct_field.into()) + } + + pub(crate) fn resolve_macro_call( + &self, + db: &dyn HirDatabase, + macro_call: InFile<&ast::MacroCall>, + ) -> Option { + let hygiene = Hygiene::new(db.upcast(), 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.upcast(), path.mod_path()).map(|it| it.into()) + } + + pub(crate) fn resolve_bind_pat_to_const( + &self, + db: &dyn HirDatabase, + pat: &ast::IdentPat, + ) -> Option { + let pat_id = self.pat_id(&pat.clone().into())?; + let body = self.body.as_ref()?; + let path = match &body[pat_id] { + Pat::Path(path) => path, + _ => return None, + }; + let res = resolve_hir_path(db, &self.resolver, &path)?; + match res { + PathResolution::Def(def) => Some(def), + _ => None, + } + } + + pub(crate) fn resolve_path( + &self, + db: &dyn HirDatabase, + path: &ast::Path, + ) -> Option { + if let Some(path_expr) = path.syntax().parent().and_then(ast::PathExpr::cast) { + let expr_id = self.expr_id(db, &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(VariantId::EnumVariantId(variant)) = + self.infer.as_ref()?.variant_resolution_for_expr(expr_id) + { + return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.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())); + } + if let Some(VariantId::EnumVariantId(variant)) = + self.infer.as_ref()?.variant_resolution_for_pat(pat_id) + { + return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into()))); + } + } + + if let Some(rec_lit) = path.syntax().parent().and_then(ast::RecordExpr::cast) { + let expr_id = self.expr_id(db, &rec_lit.into())?; + if let Some(VariantId::EnumVariantId(variant)) = + self.infer.as_ref()?.variant_resolution_for_expr(expr_id) + { + return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into()))); + } + } + + if let Some(rec_pat) = path.syntax().parent().and_then(ast::RecordPat::cast) { + let pat_id = self.pat_id(&rec_pat.into())?; + if let Some(VariantId::EnumVariantId(variant)) = + self.infer.as_ref()?.variant_resolution_for_pat(pat_id) + { + return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into()))); + } + } + + // This must be a normal source file rather than macro file. + let hir_path = Path::from_src(path.clone(), &Hygiene::new(db.upcast(), self.file_id))?; + + // Case where path is a qualifier of another path, e.g. foo::bar::Baz where we + // trying to resolve foo::bar. + if let Some(outer_path) = path.syntax().parent().and_then(ast::Path::cast) { + if let Some(qualifier) = outer_path.qualifier() { + if path == &qualifier { + return resolve_hir_path_qualifier(db, &self.resolver, &hir_path); + } + } + } + + resolve_hir_path(db, &self.resolver, &hir_path) + } + + pub(crate) fn record_literal_missing_fields( + &self, + db: &dyn HirDatabase, + literal: &ast::RecordExpr, + ) -> Option> { + let krate = self.resolver.krate()?; + let body = self.body.as_ref()?; + let infer = self.infer.as_ref()?; + + let expr_id = self.expr_id(db, &literal.clone().into())?; + let substs = match &infer.type_of_expr[expr_id] { + Ty::Apply(a_ty) => &a_ty.parameters, + _ => return None, + }; + + let (variant, missing_fields, _exhaustive) = + record_literal_missing_fields(db, infer, expr_id, &body[expr_id])?; + let res = self.missing_fields(db, krate, substs, variant, missing_fields); + Some(res) + } + + pub(crate) fn record_pattern_missing_fields( + &self, + db: &dyn HirDatabase, + pattern: &ast::RecordPat, + ) -> Option> { + let krate = self.resolver.krate()?; + let body = self.body.as_ref()?; + let infer = self.infer.as_ref()?; + + let pat_id = self.pat_id(&pattern.clone().into())?; + let substs = match &infer.type_of_pat[pat_id] { + Ty::Apply(a_ty) => &a_ty.parameters, + _ => return None, + }; + + let (variant, missing_fields, _exhaustive) = + record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?; + let res = self.missing_fields(db, krate, substs, variant, missing_fields); + Some(res) + } + + fn missing_fields( + &self, + db: &dyn HirDatabase, + krate: CrateId, + substs: &Substs, + variant: VariantId, + missing_fields: Vec, + ) -> Vec<(Field, Type)> { + 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].clone().subst(substs); + (field.into(), Type::new_with_resolver_inner(db, krate, &self.resolver, ty)) + }) + .collect() + } + + pub(crate) fn expand( + &self, + db: &dyn HirDatabase, + macro_call: InFile<&ast::MacroCall>, + ) -> Option { + let krate = self.resolver.krate()?; + let macro_call_id = macro_call.as_call_id(db.upcast(), krate, |path| { + self.resolver.resolve_path_as_macro(db.upcast(), &path) + })?; + Some(macro_call_id.as_file()).filter(|it| it.expansion_level(db.upcast()) < 64) + } + + pub(crate) fn resolve_variant( + &self, + db: &dyn HirDatabase, + record_lit: ast::RecordExpr, + ) -> Option { + let infer = self.infer.as_ref()?; + let expr_id = self.expr_id(db, &record_lit.into())?; + infer.variant_resolution_for_expr(expr_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( + db: &dyn HirDatabase, + scopes: &ExprScopes, + source_map: &BodySourceMap, + offset: InFile, +) -> Option { + 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 != offset.file_id { + return None; + } + let root = source.file_syntax(db.upcast()); + let node = source.value.to_node(&root); + Some((node.syntax().text_range(), scope)) + }) + // find containing scope + .min_by_key(|(expr_range, _scope)| { + ( + !(expr_range.start() <= offset.value && offset.value <= expr_range.end()), + expr_range.len(), + ) + }) + .map(|(expr_range, scope)| { + adjust(db, scopes, source_map, expr_range, offset).unwrap_or(*scope) + }) +} + +// 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: &BodySourceMap, + expr_range: TextRange, + offset: InFile, +) -> Option { + 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 != offset.file_id { + return None; + } + let root = source.file_syntax(db.upcast()); + let node = source.value.to_node(&root); + Some((node.syntax().text_range(), scope)) + }) + .filter(|&(range, _)| { + range.start() <= offset.value && 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)| *scope) +} + +pub(crate) fn resolve_hir_path( + db: &dyn HirDatabase, + resolver: &Resolver, + path: &Path, +) -> Option { + let types = + resolver.resolve_path_in_type_ns_fully(db.upcast(), 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 body_owner = resolver.body_owner(); + let values = + resolver.resolve_path_in_value_ns_fully(db.upcast(), path.mod_path()).and_then(|val| { + let res = match val { + ValueNs::LocalBinding(pat_id) => { + let var = Local { parent: body_owner?.into(), 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()), + ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()), + }; + Some(res) + }); + + let items = resolver + .resolve_module_path_in_items(db.upcast(), path.mod_path()) + .take_types() + .map(|it| PathResolution::Def(it.into())); + + types.or(values).or(items).or_else(|| { + resolver + .resolve_path_as_macro(db.upcast(), path.mod_path()) + .map(|def| PathResolution::Macro(def.into())) + }) +} + +/// 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. +pub(crate) fn resolve_hir_path_qualifier( + db: &dyn HirDatabase, + resolver: &Resolver, + path: &Path, +) -> Option { + let items = resolver + .resolve_module_path_in_items(db.upcast(), path.mod_path()) + .take_types() + .map(|it| PathResolution::Def(it.into())); + + if items.is_some() { + return items; + } + + resolver.resolve_path_in_type_ns_fully(db.upcast(), 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()), + }) +} -- cgit v1.2.3