mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-24 12:03:31 +00:00
Add support for extern crate
This adds syntax highlighting, hover and goto def functionality for extern crate
This commit is contained in:
parent
a69f19a6a5
commit
6cde0b1aa0
11 changed files with 225 additions and 59 deletions
|
@ -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,
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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 ")
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
40
crates/ra_ide/test_data/highlight_extern_crate.html
Normal file
40
crates/ra_ide/test_data/highlight_extern_crate.html
Normal 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>
|
|
@ -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,32 +123,37 @@ 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)?;
|
||||
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<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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue