From 6cde0b1aa0f6b8623c6b81b2396f4a0345891233 Mon Sep 17 00:00:00 2001 From: Paul Daniel Faria Date: Sat, 8 Aug 2020 14:14:18 -0400 Subject: [PATCH] Add support for extern crate This adds syntax highlighting, hover and goto def functionality for extern crate --- .../ra_assists/src/handlers/add_turbo_fish.rs | 2 +- crates/ra_hir/src/semantics.rs | 25 ++++- crates/ra_ide/src/display/short_label.rs | 6 ++ crates/ra_ide/src/goto_definition.rs | 37 +++++-- crates/ra_ide/src/hover.rs | 49 ++++++++- crates/ra_ide/src/references.rs | 4 +- crates/ra_ide/src/syntax_highlighting.rs | 2 + .../ra_ide/src/syntax_highlighting/tests.rs | 17 +++ .../test_data/highlight_extern_crate.html | 40 +++++++ crates/ra_ide_db/src/defs.rs | 100 +++++++++++------- crates/ra_ide_db/src/imports_locator.rs | 2 +- 11 files changed, 225 insertions(+), 59 deletions(-) create mode 100644 crates/ra_ide/test_data/highlight_extern_crate.html diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs index 0c565e89af..537322a72c 100644 --- a/crates/ra_assists/src/handlers/add_turbo_fish.rs +++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs @@ -41,7 +41,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( let name_ref = ast::NameRef::cast(ident.parent())?; let def = match classify_name_ref(&ctx.sema, &name_ref)? { NameRefClass::Definition(def) => def, - NameRefClass::FieldShorthand { .. } => return None, + NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None, }; let fun = match def { Definition::ModuleDef(hir::ModuleDef::Function(it)) => it, diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs index 307b336f20..e392130ab6 100644 --- a/crates/ra_hir/src/semantics.rs +++ b/crates/ra_hir/src/semantics.rs @@ -8,7 +8,7 @@ use hir_def::{ resolver::{self, HasResolver, Resolver}, AsMacroCall, FunctionId, TraitId, VariantId, }; -use hir_expand::{diagnostics::AstDiagnostic, hygiene::Hygiene, ExpansionInfo}; +use hir_expand::{diagnostics::AstDiagnostic, hygiene::Hygiene, name::AsName, ExpansionInfo}; use hir_ty::associated_type_shorthand_candidates; use itertools::Itertools; use ra_db::{FileId, FileRange}; @@ -24,8 +24,8 @@ use crate::{ diagnostics::Diagnostic, semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, source_analyzer::{resolve_hir_path, resolve_hir_path_qualifier, SourceAnalyzer}, - AssocItem, Callable, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, - ModuleDef, Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, VariantDef, + AssocItem, Callable, Crate, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, + Module, ModuleDef, Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, VariantDef, }; use resolver::TypeNs; @@ -228,6 +228,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.resolve_path(path) } + pub fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option { + self.imp.resolve_extern_crate(extern_crate) + } + pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option { self.imp.resolve_variant(record_lit).map(VariantDef::from) } @@ -443,6 +447,17 @@ impl<'db> SemanticsImpl<'db> { self.analyze(path.syntax()).resolve_path(self.db, path) } + fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option { + let krate = self.scope(extern_crate.syntax()).krate()?; + krate.dependencies(self.db).into_iter().find_map(|dep| { + if dep.name == extern_crate.name_ref()?.as_name() { + Some(dep.krate) + } else { + None + } + }) + } + fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option { self.analyze(record_lit.syntax()).resolve_variant(self.db, record_lit) } @@ -612,6 +627,10 @@ impl<'a> SemanticsScope<'a> { Some(Module { id: self.resolver.module()? }) } + pub fn krate(&self) -> Option { + Some(Crate { id: self.resolver.krate()? }) + } + /// Note: `FxHashSet` should be treated as an opaque type, passed into `Type // FIXME: rename to visible_traits to not repeat scope? pub fn traits_in_scope(&self) -> FxHashSet { diff --git a/crates/ra_ide/src/display/short_label.rs b/crates/ra_ide/src/display/short_label.rs index 0fdf8e9a58..b5ff9fa2de 100644 --- a/crates/ra_ide/src/display/short_label.rs +++ b/crates/ra_ide/src/display/short_label.rs @@ -47,6 +47,12 @@ impl ShortLabel for ast::Module { } } +impl ShortLabel for ast::SourceFile { + fn short_label(&self) -> Option { + None + } +} + impl ShortLabel for ast::TypeAlias { fn short_label(&self) -> Option { short_label_from_node(self, "type ") diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 4e3f428fae..b44b6fe22f 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs @@ -1,6 +1,6 @@ use hir::Semantics; use ra_ide_db::{ - defs::{classify_name, classify_name_ref, NameClass}, + defs::{classify_name, classify_name_ref}, symbol_index, RootDatabase, }; use ra_syntax::{ @@ -40,10 +40,7 @@ pub(crate) fn goto_definition( reference_definition(&sema, &name_ref).to_vec() }, ast::Name(name) => { - let def = match classify_name(&sema, &name)? { - NameClass::Definition(def) | NameClass::ConstReference(def) => def, - NameClass::FieldShorthand { local: _, field } => field, - }; + let def = classify_name(&sema, &name)?.definition(sema.db)?; let nav = def.try_to_nav(sema.db)?; vec![nav] }, @@ -85,9 +82,7 @@ pub(crate) fn reference_definition( name_ref: &ast::NameRef, ) -> ReferenceResult { let name_kind = classify_name_ref(sema, name_ref); - if let Some(def) = name_kind { - let def = def.definition(); - + if let Some(def) = name_kind.and_then(|def| def.definition(sema.db)) { return match def.try_to_nav(sema.db) { Some(nav) => ReferenceResult::Exact(nav), None => ReferenceResult::Approximate(Vec::new()), @@ -133,6 +128,32 @@ mod tests { assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }); } + #[test] + fn goto_def_for_extern_crate() { + check( + r#" + //- /main.rs + extern crate std<|>; + //- /std/lib.rs + // empty + //^ file + "#, + ) + } + + #[test] + fn goto_def_for_renamed_extern_crate() { + check( + r#" + //- /main.rs + extern crate std as abc<|>; + //- /std/lib.rs + // empty + //^ file + "#, + ) + } + #[test] fn goto_def_in_items() { check( diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index aa48cb412f..a632ea6a20 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -85,8 +85,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option classify_name_ref(&sema, &name_ref).map(|d| d.definition()), - ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition()), + ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).and_then(|d| d.definition(sema.db)), + ast::Name(name) => classify_name(&sema, &name).and_then(|d| d.definition(sema.db)), _ => None, } }; @@ -304,7 +304,10 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option { let docs = Documentation::from_ast(&it).map(Into::into); hover_markup(docs, it.short_label(), mod_path) } - _ => None, + ModuleSource::SourceFile(it) => { + let docs = Documentation::from_ast(&it).map(Into::into); + hover_markup(docs, it.short_label(), mod_path) + } }, ModuleDef::Function(it) => from_def_source(db, it, mod_path), ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path), @@ -1106,6 +1109,46 @@ fn bar() { fo<|>o(); } ); } + #[test] + fn test_hover_extern_crate() { + check( + r#" +//- /main.rs +extern crate st<|>d; +//- /std/lib.rs +//! Standard library for this test +//! +//! Printed? +//! abc123 + "#, + expect![[r#" + *std* + Standard library for this test + + Printed? + abc123 + "#]], + ); + check( + r#" +//- /main.rs +extern crate std as ab<|>c; +//- /std/lib.rs +//! Standard library for this test +//! +//! Printed? +//! abc123 + "#, + expect![[r#" + *abc* + Standard library for this test + + Printed? + abc123 + "#]], + ); + } + #[test] fn test_hover_mod_with_same_name_as_function() { check( diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index cf456630a5..9dd228b9c3 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs @@ -130,13 +130,13 @@ fn find_name( opt_name: Option, ) -> Option> { if let Some(name) = opt_name { - let def = classify_name(sema, &name)?.definition(); + let def = classify_name(sema, &name)?.definition(sema.db)?; let range = name.syntax().text_range(); return Some(RangeInfo::new(range, def)); } let name_ref = sema.find_node_at_offset_with_descend::(&syntax, position.offset)?; - let def = classify_name_ref(sema, &name_ref)?.definition(); + let def = classify_name_ref(sema, &name_ref)?.definition(sema.db)?; let range = name_ref.syntax().text_range(); Some(RangeInfo::new(range, def)) } diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 89efe71dab..ec442bcd83 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs @@ -483,6 +483,7 @@ fn highlight_element( }; match name_kind { + Some(NameClass::ExternCrate(_)) => HighlightTag::Module.into(), Some(NameClass::Definition(def)) => { highlight_name(db, def) | HighlightModifier::Definition } @@ -500,6 +501,7 @@ fn highlight_element( let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); match classify_name_ref(sema, &name_ref) { Some(name_kind) => match name_kind { + NameRefClass::ExternCrate(_) => HighlightTag::Module.into(), NameRefClass::Definition(def) => { if let Definition::Local(local) = &def { if let Some(name) = local.name(db) { diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index b9b3580223..b8d60bdc63 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs @@ -380,6 +380,23 @@ macro_rules! noop { ); } +#[test] +fn test_extern_crate() { + check_highlighting( + r#" + //- /main.rs + extern crate std; + extern crate alloc as abc; + //- /std/lib.rs + pub struct S; + //- /alloc/lib.rs + pub struct A + "#, + expect_file!["crates/ra_ide/test_data/highlight_extern_crate.html"], + false, + ); +} + /// Highlights the code given by the `ra_fixture` argument, renders the /// result as HTML, and compares it with the HTML file given as `snapshot`. /// Note that the `snapshot` file is overwritten by the rendered HTML. diff --git a/crates/ra_ide/test_data/highlight_extern_crate.html b/crates/ra_ide/test_data/highlight_extern_crate.html new file mode 100644 index 0000000000..800d894c76 --- /dev/null +++ b/crates/ra_ide/test_data/highlight_extern_crate.html @@ -0,0 +1,40 @@ + + +
extern crate std;
+extern crate alloc as abc;
+
\ No newline at end of file diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs index b51000b03f..e2a4f2983c 100644 --- a/crates/ra_ide_db/src/defs.rs +++ b/crates/ra_ide_db/src/defs.rs @@ -6,8 +6,8 @@ // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). use hir::{ - Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, - Semantics, TypeParam, Visibility, + db::HirDatabase, Crate, Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, + Name, PathResolution, Semantics, TypeParam, Visibility, }; use ra_prof::profile; use ra_syntax::{ @@ -80,6 +80,7 @@ impl Definition { #[derive(Debug)] pub enum NameClass { + ExternCrate(Crate), Definition(Definition), /// `None` in `if let None = Some(82) {}` ConstReference(Definition), @@ -90,19 +91,21 @@ pub enum NameClass { } impl NameClass { - pub fn into_definition(self) -> Option { - match self { - NameClass::Definition(it) => Some(it), - NameClass::ConstReference(_) => None, - NameClass::FieldShorthand { local, field: _ } => Some(Definition::Local(local)), - } + pub fn into_definition(self, db: &dyn HirDatabase) -> Option { + Some(match self { + NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db)?.into()), + NameClass::Definition(it) => it, + NameClass::ConstReference(_) => return None, + NameClass::FieldShorthand { local, field: _ } => Definition::Local(local), + }) } - pub fn definition(self) -> Definition { - match self { + pub fn definition(self, db: &dyn HirDatabase) -> Option { + Some(match self { + NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db)?.into()), NameClass::Definition(it) | NameClass::ConstReference(it) => it, NameClass::FieldShorthand { local: _, field } => field, - } + }) } } @@ -120,32 +123,37 @@ pub fn classify_name(sema: &Semantics, name: &ast::Name) -> Option match_ast! { match parent { ast::Rename(it) => { - let use_tree = it.syntax().parent().and_then(ast::UseTree::cast)?; - let path = use_tree.path()?; - let path_segment = path.segment()?; - let name_ref_class = path_segment - .name_ref() - // The rename might be from a `self` token, so fallback to the name higher - // in the use tree. - .or_else(||{ - if path_segment.self_token().is_none() { - return None; - } + if let Some(use_tree) = it.syntax().parent().and_then(ast::UseTree::cast) { + let path = use_tree.path()?; + let path_segment = path.segment()?; + let name_ref_class = path_segment + .name_ref() + // The rename might be from a `self` token, so fallback to the name higher + // in the use tree. + .or_else(||{ + if path_segment.self_token().is_none() { + return None; + } - let use_tree = use_tree - .syntax() - .parent() - .as_ref() - // Skip over UseTreeList - .and_then(SyntaxNode::parent) - .and_then(ast::UseTree::cast)?; - let path = use_tree.path()?; - let path_segment = path.segment()?; - path_segment.name_ref() - }) - .and_then(|name_ref| classify_name_ref(sema, &name_ref))?; + let use_tree = use_tree + .syntax() + .parent() + .as_ref() + // Skip over UseTreeList + .and_then(SyntaxNode::parent) + .and_then(ast::UseTree::cast)?; + let path = use_tree.path()?; + let path_segment = path.segment()?; + path_segment.name_ref() + }) + .and_then(|name_ref| classify_name_ref(sema, &name_ref))?; - Some(NameClass::Definition(name_ref_class.definition())) + Some(NameClass::Definition(name_ref_class.definition(sema.db)?)) + } else { + let extern_crate = it.syntax().parent().and_then(ast::ExternCrate::cast)?; + let resolved = sema.resolve_extern_crate(&extern_crate)?; + Some(NameClass::ExternCrate(resolved)) + } }, ast::IdentPat(it) => { let local = sema.to_def(&it)?; @@ -220,16 +228,20 @@ pub fn classify_name(sema: &Semantics, name: &ast::Name) -> Option #[derive(Debug)] pub enum NameRefClass { + ExternCrate(Crate), Definition(Definition), FieldShorthand { local: Local, field: Definition }, } impl NameRefClass { - pub fn definition(self) -> Definition { - match self { + pub fn definition(self, db: &dyn HirDatabase) -> Option { + Some(match self { + NameRefClass::ExternCrate(krate) => { + Definition::ModuleDef(krate.root_module(db)?.into()) + } NameRefClass::Definition(def) => def, NameRefClass::FieldShorthand { local, field: _ } => Definition::Local(local), - } + }) } } @@ -307,9 +319,15 @@ pub fn classify_name_ref( } } - let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; - let resolved = sema.resolve_path(&path)?; - Some(NameRefClass::Definition(resolved.into())) + if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { + if let Some(resolved) = sema.resolve_path(&path) { + return Some(NameRefClass::Definition(resolved.into())); + } + } + + let extern_crate = ast::ExternCrate::cast(parent)?; + let resolved = sema.resolve_extern_crate(&extern_crate)?; + Some(NameRefClass::ExternCrate(resolved)) } impl From for Definition { diff --git a/crates/ra_ide_db/src/imports_locator.rs b/crates/ra_ide_db/src/imports_locator.rs index 1fba71ff85..9e040973b3 100644 --- a/crates/ra_ide_db/src/imports_locator.rs +++ b/crates/ra_ide_db/src/imports_locator.rs @@ -61,5 +61,5 @@ fn get_name_definition<'a>( candidate_node }; let name = ast::Name::cast(candidate_name_node)?; - classify_name(sema, &name)?.into_definition() + classify_name(sema, &name)?.into_definition(sema.db) }