Add support for extern crate

This adds syntax highlighting, hover and goto def
functionality for extern crate
This commit is contained in:
Paul Daniel Faria 2020-08-08 14:14:18 -04:00
parent a69f19a6a5
commit 6cde0b1aa0
11 changed files with 225 additions and 59 deletions

View file

@ -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 name_ref = ast::NameRef::cast(ident.parent())?;
let def = match classify_name_ref(&ctx.sema, &name_ref)? { let def = match classify_name_ref(&ctx.sema, &name_ref)? {
NameRefClass::Definition(def) => def, NameRefClass::Definition(def) => def,
NameRefClass::FieldShorthand { .. } => return None, NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None,
}; };
let fun = match def { let fun = match def {
Definition::ModuleDef(hir::ModuleDef::Function(it)) => it, Definition::ModuleDef(hir::ModuleDef::Function(it)) => it,

View file

@ -8,7 +8,7 @@ use hir_def::{
resolver::{self, HasResolver, Resolver}, resolver::{self, HasResolver, Resolver},
AsMacroCall, FunctionId, TraitId, VariantId, 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 hir_ty::associated_type_shorthand_candidates;
use itertools::Itertools; use itertools::Itertools;
use ra_db::{FileId, FileRange}; use ra_db::{FileId, FileRange};
@ -24,8 +24,8 @@ use crate::{
diagnostics::Diagnostic, diagnostics::Diagnostic,
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
source_analyzer::{resolve_hir_path, resolve_hir_path_qualifier, SourceAnalyzer}, source_analyzer::{resolve_hir_path, resolve_hir_path_qualifier, SourceAnalyzer},
AssocItem, Callable, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, AssocItem, Callable, Crate, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef,
ModuleDef, Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, VariantDef, Module, ModuleDef, Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, VariantDef,
}; };
use resolver::TypeNs; use resolver::TypeNs;
@ -228,6 +228,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.imp.resolve_path(path) self.imp.resolve_path(path)
} }
pub fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> {
self.imp.resolve_extern_crate(extern_crate)
}
pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantDef> { pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantDef> {
self.imp.resolve_variant(record_lit).map(VariantDef::from) 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) self.analyze(path.syntax()).resolve_path(self.db, path)
} }
fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> {
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<VariantId> { fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantId> {
self.analyze(record_lit.syntax()).resolve_variant(self.db, record_lit) 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()? }) Some(Module { id: self.resolver.module()? })
} }
pub fn krate(&self) -> Option<Crate> {
Some(Crate { id: self.resolver.krate()? })
}
/// Note: `FxHashSet<TraitId>` should be treated as an opaque type, passed into `Type /// Note: `FxHashSet<TraitId>` should be treated as an opaque type, passed into `Type
// FIXME: rename to visible_traits to not repeat scope? // FIXME: rename to visible_traits to not repeat scope?
pub fn traits_in_scope(&self) -> FxHashSet<TraitId> { pub fn traits_in_scope(&self) -> FxHashSet<TraitId> {

View file

@ -47,6 +47,12 @@ impl ShortLabel for ast::Module {
} }
} }
impl ShortLabel for ast::SourceFile {
fn short_label(&self) -> Option<String> {
None
}
}
impl ShortLabel for ast::TypeAlias { impl ShortLabel for ast::TypeAlias {
fn short_label(&self) -> Option<String> { fn short_label(&self) -> Option<String> {
short_label_from_node(self, "type ") short_label_from_node(self, "type ")

View file

@ -1,6 +1,6 @@
use hir::Semantics; use hir::Semantics;
use ra_ide_db::{ use ra_ide_db::{
defs::{classify_name, classify_name_ref, NameClass}, defs::{classify_name, classify_name_ref},
symbol_index, RootDatabase, symbol_index, RootDatabase,
}; };
use ra_syntax::{ use ra_syntax::{
@ -40,10 +40,7 @@ pub(crate) fn goto_definition(
reference_definition(&sema, &name_ref).to_vec() reference_definition(&sema, &name_ref).to_vec()
}, },
ast::Name(name) => { ast::Name(name) => {
let def = match classify_name(&sema, &name)? { let def = classify_name(&sema, &name)?.definition(sema.db)?;
NameClass::Definition(def) | NameClass::ConstReference(def) => def,
NameClass::FieldShorthand { local: _, field } => field,
};
let nav = def.try_to_nav(sema.db)?; let nav = def.try_to_nav(sema.db)?;
vec![nav] vec![nav]
}, },
@ -85,9 +82,7 @@ pub(crate) fn reference_definition(
name_ref: &ast::NameRef, name_ref: &ast::NameRef,
) -> ReferenceResult { ) -> ReferenceResult {
let name_kind = classify_name_ref(sema, name_ref); let name_kind = classify_name_ref(sema, name_ref);
if let Some(def) = name_kind { if let Some(def) = name_kind.and_then(|def| def.definition(sema.db)) {
let def = def.definition();
return match def.try_to_nav(sema.db) { return match def.try_to_nav(sema.db) {
Some(nav) => ReferenceResult::Exact(nav), Some(nav) => ReferenceResult::Exact(nav),
None => ReferenceResult::Approximate(Vec::new()), 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() }); 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] #[test]
fn goto_def_in_items() { fn goto_def_in_items() {
check( check(

View file

@ -85,8 +85,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
let node = token.parent(); let node = token.parent();
let definition = match_ast! { let definition = match_ast! {
match node { match node {
ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).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).map(|d| d.definition()), ast::Name(name) => classify_name(&sema, &name).and_then(|d| d.definition(sema.db)),
_ => None, _ => None,
} }
}; };
@ -304,7 +304,10 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
let docs = Documentation::from_ast(&it).map(Into::into); let docs = Documentation::from_ast(&it).map(Into::into);
hover_markup(docs, it.short_label(), mod_path) 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::Function(it) => from_def_source(db, it, mod_path),
ModuleDef::Adt(Adt::Struct(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] #[test]
fn test_hover_mod_with_same_name_as_function() { fn test_hover_mod_with_same_name_as_function() {
check( check(

View file

@ -130,13 +130,13 @@ fn find_name(
opt_name: Option<ast::Name>, opt_name: Option<ast::Name>,
) -> Option<RangeInfo<Definition>> { ) -> Option<RangeInfo<Definition>> {
if let Some(name) = opt_name { 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(); let range = name.syntax().text_range();
return Some(RangeInfo::new(range, def)); return Some(RangeInfo::new(range, def));
} }
let name_ref = let name_ref =
sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; sema.find_node_at_offset_with_descend::<ast::NameRef>(&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(); let range = name_ref.syntax().text_range();
Some(RangeInfo::new(range, def)) Some(RangeInfo::new(range, def))
} }

View file

@ -483,6 +483,7 @@ fn highlight_element(
}; };
match name_kind { match name_kind {
Some(NameClass::ExternCrate(_)) => HighlightTag::Module.into(),
Some(NameClass::Definition(def)) => { Some(NameClass::Definition(def)) => {
highlight_name(db, def) | HighlightModifier::Definition 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(); let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
match classify_name_ref(sema, &name_ref) { match classify_name_ref(sema, &name_ref) {
Some(name_kind) => match name_kind { Some(name_kind) => match name_kind {
NameRefClass::ExternCrate(_) => HighlightTag::Module.into(),
NameRefClass::Definition(def) => { NameRefClass::Definition(def) => {
if let Definition::Local(local) = &def { if let Definition::Local(local) = &def {
if let Some(name) = local.name(db) { if let Some(name) = local.name(db) {

View file

@ -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 /// Highlights the code given by the `ra_fixture` argument, renders the
/// result as HTML, and compares it with the HTML file given as `snapshot`. /// result as HTML, and compares it with the HTML file given as `snapshot`.
/// Note that the `snapshot` file is overwritten by the rendered HTML. /// Note that the `snapshot` file is overwritten by the rendered HTML.

View file

@ -0,0 +1,40 @@
<style>
body { margin: 0; }
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
.lifetime { color: #DFAF8F; font-style: italic; }
.comment { color: #7F9F7F; }
.documentation { color: #629755; }
.injected { opacity: 0.65 ; }
.struct, .enum { color: #7CB8BB; }
.enum_variant { color: #BDE0F3; }
.string_literal { color: #CC9393; }
.field { color: #94BFF3; }
.function { color: #93E0E3; }
.function.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.builtin_type { color: #8CD0D3; }
.type_param { color: #DFAF8F; }
.attribute { color: #94BFF3; }
.numeric_literal { color: #BFEBBF; }
.bool_literal { color: #BFE6EB; }
.macro { color: #94BFF3; }
.module { color: #AFD8AF; }
.value_param { color: #DCDCCC; }
.variable { color: #DCDCCC; }
.format_specifier { color: #CC696B; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
</style>
<pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module">std</span><span class="punctuation">;</span>
<span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module">alloc</span> <span class="keyword">as</span> <span class="module">abc</span><span class="punctuation">;</span>
</code></pre>

View file

@ -6,8 +6,8 @@
// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
use hir::{ use hir::{
Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, db::HirDatabase, Crate, Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef,
Semantics, TypeParam, Visibility, Name, PathResolution, Semantics, TypeParam, Visibility,
}; };
use ra_prof::profile; use ra_prof::profile;
use ra_syntax::{ use ra_syntax::{
@ -80,6 +80,7 @@ impl Definition {
#[derive(Debug)] #[derive(Debug)]
pub enum NameClass { pub enum NameClass {
ExternCrate(Crate),
Definition(Definition), Definition(Definition),
/// `None` in `if let None = Some(82) {}` /// `None` in `if let None = Some(82) {}`
ConstReference(Definition), ConstReference(Definition),
@ -90,19 +91,21 @@ pub enum NameClass {
} }
impl NameClass { impl NameClass {
pub fn into_definition(self) -> Option<Definition> { pub fn into_definition(self, db: &dyn HirDatabase) -> Option<Definition> {
match self { Some(match self {
NameClass::Definition(it) => Some(it), NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db)?.into()),
NameClass::ConstReference(_) => None, NameClass::Definition(it) => it,
NameClass::FieldShorthand { local, field: _ } => Some(Definition::Local(local)), NameClass::ConstReference(_) => return None,
} NameClass::FieldShorthand { local, field: _ } => Definition::Local(local),
})
} }
pub fn definition(self) -> Definition { pub fn definition(self, db: &dyn HirDatabase) -> Option<Definition> {
match self { Some(match self {
NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db)?.into()),
NameClass::Definition(it) | NameClass::ConstReference(it) => it, NameClass::Definition(it) | NameClass::ConstReference(it) => it,
NameClass::FieldShorthand { local: _, field } => field, NameClass::FieldShorthand { local: _, field } => field,
} })
} }
} }
@ -120,32 +123,37 @@ pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option
match_ast! { match_ast! {
match parent { match parent {
ast::Rename(it) => { ast::Rename(it) => {
let use_tree = it.syntax().parent().and_then(ast::UseTree::cast)?; if let Some(use_tree) = it.syntax().parent().and_then(ast::UseTree::cast) {
let path = use_tree.path()?; let path = use_tree.path()?;
let path_segment = path.segment()?; let path_segment = path.segment()?;
let name_ref_class = path_segment let name_ref_class = path_segment
.name_ref() .name_ref()
// The rename might be from a `self` token, so fallback to the name higher // The rename might be from a `self` token, so fallback to the name higher
// in the use tree. // in the use tree.
.or_else(||{ .or_else(||{
if path_segment.self_token().is_none() { if path_segment.self_token().is_none() {
return None; return None;
} }
let use_tree = use_tree let use_tree = use_tree
.syntax() .syntax()
.parent() .parent()
.as_ref() .as_ref()
// Skip over UseTreeList // Skip over UseTreeList
.and_then(SyntaxNode::parent) .and_then(SyntaxNode::parent)
.and_then(ast::UseTree::cast)?; .and_then(ast::UseTree::cast)?;
let path = use_tree.path()?; let path = use_tree.path()?;
let path_segment = path.segment()?; let path_segment = path.segment()?;
path_segment.name_ref() path_segment.name_ref()
}) })
.and_then(|name_ref| classify_name_ref(sema, &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) => { ast::IdentPat(it) => {
let local = sema.to_def(&it)?; let local = sema.to_def(&it)?;
@ -220,16 +228,20 @@ pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option
#[derive(Debug)] #[derive(Debug)]
pub enum NameRefClass { pub enum NameRefClass {
ExternCrate(Crate),
Definition(Definition), Definition(Definition),
FieldShorthand { local: Local, field: Definition }, FieldShorthand { local: Local, field: Definition },
} }
impl NameRefClass { impl NameRefClass {
pub fn definition(self) -> Definition { pub fn definition(self, db: &dyn HirDatabase) -> Option<Definition> {
match self { Some(match self {
NameRefClass::ExternCrate(krate) => {
Definition::ModuleDef(krate.root_module(db)?.into())
}
NameRefClass::Definition(def) => def, NameRefClass::Definition(def) => def,
NameRefClass::FieldShorthand { local, field: _ } => Definition::Local(local), 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)?; if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) {
let resolved = sema.resolve_path(&path)?; if let Some(resolved) = sema.resolve_path(&path) {
Some(NameRefClass::Definition(resolved.into())) 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<PathResolution> for Definition { impl From<PathResolution> for Definition {

View file

@ -61,5 +61,5 @@ fn get_name_definition<'a>(
candidate_node candidate_node
}; };
let name = ast::Name::cast(candidate_name_node)?; let name = ast::Name::cast(candidate_name_node)?;
classify_name(sema, &name)?.into_definition() classify_name(sema, &name)?.into_definition(sema.db)
} }