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 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,

View file

@ -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<Crate> {
self.imp.resolve_extern_crate(extern_crate)
}
pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantDef> {
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<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> {
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<Crate> {
Some(Crate { id: self.resolver.krate()? })
}
/// Note: `FxHashSet<TraitId>` 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<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 {
fn short_label(&self) -> Option<String> {
short_label_from_node(self, "type ")

View file

@ -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(

View file

@ -85,8 +85,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
let node = token.parent();
let definition = match_ast! {
match node {
ast::NameRef(name_ref) => 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<Markup> {
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(

View file

@ -130,13 +130,13 @@ fn find_name(
opt_name: Option<ast::Name>,
) -> Option<RangeInfo<Definition>> {
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::<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();
Some(RangeInfo::new(range, def))
}

View file

@ -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) {

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
/// result as HTML, and compares it with the HTML file given as `snapshot`.
/// 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).
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<Definition> {
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<Definition> {
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<Definition> {
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,7 +123,7 @@ pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option
match_ast! {
match parent {
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_segment = path.segment()?;
let name_ref_class = path_segment
@ -145,7 +148,12 @@ pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option
})
.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<RootDatabase>, 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<Definition> {
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<PathResolution> for Definition {

View file

@ -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)
}