Merge #816
816: Prelude & Edition 2015 import resolution r=matklad a=flodiebold I implemented the prelude import, but it turned out to be useless without being able to resolve any of the imports in the prelude 😅 So I had to add some edition handling and handle 2015-style imports (at least the simplified scheme proposed in rust-lang/rust#57745). So now finally `Option` resolves 😄 One remaining problem is that we don't actually know the edition for sysroot crates. They're currently hardcoded to 2015, but there's already a bunch of PRs upgrading the editions of various rustc crates, so we'll have to detect the edition somehow, or just change the hardcoding to 2018 later, I guess... ~Also currently missing is completion for prelude names, though that shouldn't be hard to add. And `Vec` still doesn't resolve, so I need to look into that.~ Co-authored-by: Florian Diebold <flodiebold@gmail.com>
This commit is contained in:
commit
cb4327b3a9
@ -56,15 +56,31 @@ pub struct CyclicDependencies;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct CrateId(pub u32);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Edition {
|
||||
Edition2018,
|
||||
Edition2015,
|
||||
}
|
||||
|
||||
impl Edition {
|
||||
pub fn from_string(s: &str) -> Edition {
|
||||
match s {
|
||||
"2015" => Edition::Edition2015,
|
||||
"2018" | _ => Edition::Edition2018,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct CrateData {
|
||||
file_id: FileId,
|
||||
edition: Edition,
|
||||
dependencies: Vec<Dependency>,
|
||||
}
|
||||
|
||||
impl CrateData {
|
||||
fn new(file_id: FileId) -> CrateData {
|
||||
CrateData { file_id, dependencies: Vec::new() }
|
||||
fn new(file_id: FileId, edition: Edition) -> CrateData {
|
||||
CrateData { file_id, edition, dependencies: Vec::new() }
|
||||
}
|
||||
|
||||
fn add_dep(&mut self, name: SmolStr, crate_id: CrateId) {
|
||||
@ -85,9 +101,9 @@ impl Dependency {
|
||||
}
|
||||
|
||||
impl CrateGraph {
|
||||
pub fn add_crate_root(&mut self, file_id: FileId) -> CrateId {
|
||||
pub fn add_crate_root(&mut self, file_id: FileId, edition: Edition) -> CrateId {
|
||||
let crate_id = CrateId(self.arena.len() as u32);
|
||||
let prev = self.arena.insert(crate_id, CrateData::new(file_id));
|
||||
let prev = self.arena.insert(crate_id, CrateData::new(file_id, edition));
|
||||
assert!(prev.is_none());
|
||||
crate_id
|
||||
}
|
||||
@ -112,6 +128,10 @@ impl CrateGraph {
|
||||
self.arena[&crate_id].file_id
|
||||
}
|
||||
|
||||
pub fn edition(&self, crate_id: CrateId) -> Edition {
|
||||
self.arena[&crate_id].edition
|
||||
}
|
||||
|
||||
// TODO: this only finds one crate with the given root; we could have multiple
|
||||
pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option<CrateId> {
|
||||
let (&crate_id, _) = self.arena.iter().find(|(_crate_id, data)| data.file_id == file_id)?;
|
||||
@ -159,14 +179,14 @@ impl CrateGraph {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{CrateGraph, FileId, SmolStr};
|
||||
use super::{CrateGraph, FileId, SmolStr, Edition::Edition2018};
|
||||
|
||||
#[test]
|
||||
fn it_should_painc_because_of_cycle_dependencies() {
|
||||
fn it_should_panic_because_of_cycle_dependencies() {
|
||||
let mut graph = CrateGraph::default();
|
||||
let crate1 = graph.add_crate_root(FileId(1u32));
|
||||
let crate2 = graph.add_crate_root(FileId(2u32));
|
||||
let crate3 = graph.add_crate_root(FileId(3u32));
|
||||
let crate1 = graph.add_crate_root(FileId(1u32), Edition2018);
|
||||
let crate2 = graph.add_crate_root(FileId(2u32), Edition2018);
|
||||
let crate3 = graph.add_crate_root(FileId(3u32), Edition2018);
|
||||
assert!(graph.add_dep(crate1, SmolStr::new("crate2"), crate2).is_ok());
|
||||
assert!(graph.add_dep(crate2, SmolStr::new("crate3"), crate3).is_ok());
|
||||
assert!(graph.add_dep(crate3, SmolStr::new("crate1"), crate1).is_err());
|
||||
@ -175,9 +195,9 @@ mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut graph = CrateGraph::default();
|
||||
let crate1 = graph.add_crate_root(FileId(1u32));
|
||||
let crate2 = graph.add_crate_root(FileId(2u32));
|
||||
let crate3 = graph.add_crate_root(FileId(3u32));
|
||||
let crate1 = graph.add_crate_root(FileId(1u32), Edition2018);
|
||||
let crate2 = graph.add_crate_root(FileId(2u32), Edition2018);
|
||||
let crate3 = graph.add_crate_root(FileId(3u32), Edition2018);
|
||||
assert!(graph.add_dep(crate1, SmolStr::new("crate2"), crate2).is_ok());
|
||||
assert!(graph.add_dep(crate2, SmolStr::new("crate3"), crate3).is_ok());
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ pub use ::salsa as salsa;
|
||||
pub use crate::{
|
||||
cancellation::Canceled,
|
||||
input::{
|
||||
FileId, CrateId, SourceRoot, SourceRootId, CrateGraph, Dependency,
|
||||
FileId, CrateId, SourceRoot, SourceRootId, CrateGraph, Dependency, Edition,
|
||||
},
|
||||
loc2id::LocationIntener,
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use relative_path::RelativePathBuf;
|
||||
use ra_db::{CrateId, FileId, SourceRootId};
|
||||
use ra_db::{CrateId, FileId, SourceRootId, Edition};
|
||||
use ra_syntax::{ast::self, TreeArc, SyntaxNode};
|
||||
|
||||
use crate::{
|
||||
@ -38,13 +38,20 @@ impl Crate {
|
||||
pub fn crate_id(&self) -> CrateId {
|
||||
self.crate_id
|
||||
}
|
||||
|
||||
pub fn dependencies(&self, db: &impl PersistentHirDatabase) -> Vec<CrateDependency> {
|
||||
self.dependencies_impl(db)
|
||||
}
|
||||
|
||||
pub fn root_module(&self, db: &impl PersistentHirDatabase) -> Option<Module> {
|
||||
self.root_module_impl(db)
|
||||
}
|
||||
|
||||
pub fn edition(&self, db: &impl PersistentHirDatabase) -> Edition {
|
||||
let crate_graph = db.crate_graph();
|
||||
crate_graph.edition(self.crate_id)
|
||||
}
|
||||
|
||||
// TODO: should this be in source_binder?
|
||||
pub fn source_root_crates(
|
||||
db: &impl PersistentHirDatabase,
|
||||
|
@ -6,4 +6,5 @@ test_utils::marks!(
|
||||
type_var_resolves_to_int_var
|
||||
glob_enum
|
||||
glob_across_crates
|
||||
std_prelude
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ use std::{sync::Arc, panic};
|
||||
use parking_lot::Mutex;
|
||||
use ra_db::{
|
||||
FilePosition, FileId, CrateGraph, SourceRoot, SourceRootId, SourceDatabase, salsa,
|
||||
Edition,
|
||||
};
|
||||
use relative_path::RelativePathBuf;
|
||||
use test_utils::{parse_fixture, CURSOR_MARKER, extract_offset};
|
||||
@ -58,12 +59,12 @@ impl MockDatabase {
|
||||
pub fn set_crate_graph_from_fixture(&mut self, graph: CrateGraphFixture) {
|
||||
let mut ids = FxHashMap::default();
|
||||
let mut crate_graph = CrateGraph::default();
|
||||
for (crate_name, (crate_root, _)) in graph.0.iter() {
|
||||
for (crate_name, (crate_root, edition, _)) in graph.0.iter() {
|
||||
let crate_root = self.file_id_of(&crate_root);
|
||||
let crate_id = crate_graph.add_crate_root(crate_root);
|
||||
let crate_id = crate_graph.add_crate_root(crate_root, *edition);
|
||||
ids.insert(crate_name, crate_id);
|
||||
}
|
||||
for (crate_name, (_, deps)) in graph.0.iter() {
|
||||
for (crate_name, (_, _, deps)) in graph.0.iter() {
|
||||
let from = ids[crate_name];
|
||||
for dep in deps {
|
||||
let to = ids[dep];
|
||||
@ -144,7 +145,7 @@ impl MockDatabase {
|
||||
|
||||
if is_crate_root {
|
||||
let mut crate_graph = CrateGraph::default();
|
||||
crate_graph.add_crate_root(file_id);
|
||||
crate_graph.add_crate_root(file_id, Edition::Edition2018);
|
||||
self.set_crate_graph(Arc::new(crate_graph));
|
||||
}
|
||||
file_id
|
||||
@ -232,16 +233,19 @@ impl MockDatabase {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CrateGraphFixture(pub FxHashMap<String, (String, Vec<String>)>);
|
||||
pub struct CrateGraphFixture(pub FxHashMap<String, (String, Edition, Vec<String>)>);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! crate_graph {
|
||||
($($crate_name:literal: ($crate_path:literal, [$($dep:literal),*]),)*) => {{
|
||||
($($crate_name:literal: ($crate_path:literal, $($edition:literal,)? [$($dep:literal),*]),)*) => {{
|
||||
let mut res = $crate::mock::CrateGraphFixture::default();
|
||||
$(
|
||||
#[allow(unused_mut, unused_assignments)]
|
||||
let mut edition = ra_db::Edition::Edition2018;
|
||||
$(edition = ra_db::Edition::from_string($edition);)?
|
||||
res.0.insert(
|
||||
$crate_name.to_string(),
|
||||
($crate_path.to_string(), vec![$($dep.to_string()),*])
|
||||
($crate_path.to_string(), edition, vec![$($dep.to_string()),*])
|
||||
);
|
||||
)*
|
||||
res
|
||||
|
@ -18,10 +18,12 @@ pub(crate) mod lower;
|
||||
|
||||
use std::{time, sync::Arc};
|
||||
|
||||
use ra_arena::map::ArenaMap;
|
||||
use test_utils::tested_by;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ra_arena::map::ArenaMap;
|
||||
use ra_db::Edition;
|
||||
use test_utils::tested_by;
|
||||
|
||||
use crate::{
|
||||
Module, ModuleDef,
|
||||
Path, PathKind, PersistentHirDatabase,
|
||||
@ -32,8 +34,13 @@ use crate::{
|
||||
|
||||
/// `ItemMap` is the result of module name resolution. It contains, for each
|
||||
/// module, the set of visible items.
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ItemMap {
|
||||
edition: Edition,
|
||||
/// The prelude module for this crate. This either comes from an import
|
||||
/// marked with the `prelude_import` attribute, or (in the normal case) from
|
||||
/// a dependency (`std` or `core`).
|
||||
pub(crate) prelude: Option<Module>,
|
||||
pub(crate) extern_prelude: FxHashMap<Name, ModuleDef>,
|
||||
per_module: ArenaMap<ModuleId, ModuleScope>,
|
||||
}
|
||||
@ -176,7 +183,12 @@ where
|
||||
module_tree,
|
||||
processed_imports: FxHashSet::default(),
|
||||
glob_imports: FxHashMap::default(),
|
||||
result: ItemMap::default(),
|
||||
result: ItemMap {
|
||||
edition: krate.edition(db),
|
||||
prelude: None,
|
||||
extern_prelude: FxHashMap::default(),
|
||||
per_module: ArenaMap::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,6 +223,13 @@ where
|
||||
if let Some(module) = dep.krate.root_module(self.db) {
|
||||
self.result.extern_prelude.insert(dep.name.clone(), module.into());
|
||||
}
|
||||
// look for the prelude
|
||||
if self.result.prelude.is_none() {
|
||||
let item_map = self.db.item_map(dep.krate);
|
||||
if item_map.prelude.is_some() {
|
||||
self.result.prelude = item_map.prelude;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,10 +285,20 @@ where
|
||||
import_id: ImportId,
|
||||
import: &ImportData,
|
||||
) -> ReachedFixedPoint {
|
||||
log::debug!("resolving import: {:?}", import);
|
||||
log::debug!("resolving import: {:?} ({:?})", import, self.result.edition);
|
||||
let original_module = Module { krate: self.krate, module_id };
|
||||
let (def, reached_fixedpoint) =
|
||||
self.result.resolve_path_fp(self.db, original_module, &import.path);
|
||||
|
||||
let (def, reached_fixedpoint) = if import.is_extern_crate {
|
||||
let res = self.result.resolve_name_in_extern_prelude(
|
||||
&import
|
||||
.path
|
||||
.as_ident()
|
||||
.expect("extern crate should have been desugared to one-element path"),
|
||||
);
|
||||
(res, if res.is_none() { ReachedFixedPoint::No } else { ReachedFixedPoint::Yes })
|
||||
} else {
|
||||
self.result.resolve_path_fp(self.db, ResolveMode::Import, original_module, &import.path)
|
||||
};
|
||||
|
||||
if reached_fixedpoint != ReachedFixedPoint::Yes {
|
||||
return reached_fixedpoint;
|
||||
@ -279,7 +308,10 @@ where
|
||||
log::debug!("glob import: {:?}", import);
|
||||
match def.take_types() {
|
||||
Some(ModuleDef::Module(m)) => {
|
||||
if m.krate != self.krate {
|
||||
if import.is_prelude {
|
||||
tested_by!(std_prelude);
|
||||
self.result.prelude = Some(m);
|
||||
} else if m.krate != self.krate {
|
||||
tested_by!(glob_across_crates);
|
||||
// glob import from other crate => we can just import everything once
|
||||
let item_map = self.db.item_map(m.krate);
|
||||
@ -403,6 +435,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ResolveMode {
|
||||
Import,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ReachedFixedPoint {
|
||||
Yes,
|
||||
@ -431,15 +469,61 @@ impl ItemMap {
|
||||
original_module: Module,
|
||||
path: &Path,
|
||||
) -> PerNs<ModuleDef> {
|
||||
self.resolve_path_fp(db, original_module, path).0
|
||||
self.resolve_path_fp(db, ResolveMode::Other, original_module, path).0
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_name_in_module(&self, module: Module, name: &Name) -> PerNs<ModuleDef> {
|
||||
fn resolve_in_prelude(
|
||||
&self,
|
||||
db: &impl PersistentHirDatabase,
|
||||
original_module: Module,
|
||||
name: &Name,
|
||||
) -> PerNs<ModuleDef> {
|
||||
if let Some(prelude) = self.prelude {
|
||||
let resolution = if prelude.krate == original_module.krate {
|
||||
self[prelude.module_id].items.get(name).cloned()
|
||||
} else {
|
||||
db.item_map(prelude.krate)[prelude.module_id].items.get(name).cloned()
|
||||
};
|
||||
resolution.map(|r| r.def).unwrap_or_else(PerNs::none)
|
||||
} else {
|
||||
PerNs::none()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_name_in_module(
|
||||
&self,
|
||||
db: &impl PersistentHirDatabase,
|
||||
module: Module,
|
||||
name: &Name,
|
||||
) -> PerNs<ModuleDef> {
|
||||
// Resolve in:
|
||||
// - current module / scope
|
||||
// - extern prelude
|
||||
// - std prelude
|
||||
let from_scope = self[module.module_id].items.get(name).map_or(PerNs::none(), |it| it.def);
|
||||
let from_extern_prelude =
|
||||
self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it));
|
||||
let from_prelude = self.resolve_in_prelude(db, module, name);
|
||||
|
||||
from_scope.or(from_extern_prelude)
|
||||
from_scope.or(from_extern_prelude).or(from_prelude)
|
||||
}
|
||||
|
||||
fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs<ModuleDef> {
|
||||
self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it))
|
||||
}
|
||||
|
||||
fn resolve_name_in_crate_root_or_extern_prelude(
|
||||
&self,
|
||||
db: &impl PersistentHirDatabase,
|
||||
module: Module,
|
||||
name: &Name,
|
||||
) -> PerNs<ModuleDef> {
|
||||
let crate_root = module.crate_root(db);
|
||||
let from_crate_root =
|
||||
self[crate_root.module_id].items.get(name).map_or(PerNs::none(), |it| it.def);
|
||||
let from_extern_prelude = self.resolve_name_in_extern_prelude(name);
|
||||
|
||||
from_crate_root.or(from_extern_prelude)
|
||||
}
|
||||
|
||||
// Returns Yes if we are sure that additions to `ItemMap` wouldn't change
|
||||
@ -447,6 +531,7 @@ impl ItemMap {
|
||||
fn resolve_path_fp(
|
||||
&self,
|
||||
db: &impl PersistentHirDatabase,
|
||||
mode: ResolveMode,
|
||||
original_module: Module,
|
||||
path: &Path,
|
||||
) -> (PerNs<ModuleDef>, ReachedFixedPoint) {
|
||||
@ -454,12 +539,32 @@ impl ItemMap {
|
||||
let mut curr_per_ns: PerNs<ModuleDef> = match path.kind {
|
||||
PathKind::Crate => PerNs::types(original_module.crate_root(db).into()),
|
||||
PathKind::Self_ => PerNs::types(original_module.into()),
|
||||
// plain import or absolute path in 2015: crate-relative with
|
||||
// fallback to extern prelude (with the simplification in
|
||||
// rust-lang/rust#57745)
|
||||
// TODO there must be a nicer way to write this condition
|
||||
PathKind::Plain | PathKind::Abs
|
||||
if self.edition == Edition::Edition2015
|
||||
&& (path.kind == PathKind::Abs || mode == ResolveMode::Import) =>
|
||||
{
|
||||
let segment = match segments.next() {
|
||||
Some((_, segment)) => segment,
|
||||
None => return (PerNs::none(), ReachedFixedPoint::Yes),
|
||||
};
|
||||
log::debug!("resolving {:?} in crate root (+ extern prelude)", segment);
|
||||
self.resolve_name_in_crate_root_or_extern_prelude(
|
||||
db,
|
||||
original_module,
|
||||
&segment.name,
|
||||
)
|
||||
}
|
||||
PathKind::Plain => {
|
||||
let segment = match segments.next() {
|
||||
Some((_, segment)) => segment,
|
||||
None => return (PerNs::none(), ReachedFixedPoint::Yes),
|
||||
};
|
||||
self.resolve_name_in_module(original_module, &segment.name)
|
||||
log::debug!("resolving {:?} in module", segment);
|
||||
self.resolve_name_in_module(db, original_module, &segment.name)
|
||||
}
|
||||
PathKind::Super => {
|
||||
if let Some(p) = original_module.parent(db) {
|
||||
|
@ -2,13 +2,13 @@ use std::sync::Arc;
|
||||
|
||||
use ra_syntax::{
|
||||
AstNode, SourceFile, TreeArc, AstPtr,
|
||||
ast::{self, ModuleItemOwner, NameOwner},
|
||||
ast::{self, ModuleItemOwner, NameOwner, AttrsOwner},
|
||||
};
|
||||
use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
SourceItemId, Path, PathKind, ModuleSource, Name,
|
||||
SourceItemId, Path, ModuleSource, Name,
|
||||
HirFileId, MacroCallLoc, AsName, PerNs, Function,
|
||||
ModuleDef, Module, Struct, Enum, Const, Static, Trait, Type,
|
||||
ids::LocationCtx, PersistentHirDatabase,
|
||||
@ -23,6 +23,7 @@ pub(super) struct ImportData {
|
||||
pub(super) path: Path,
|
||||
pub(super) alias: Option<Name>,
|
||||
pub(super) is_glob: bool,
|
||||
pub(super) is_prelude: bool,
|
||||
pub(super) is_extern_crate: bool,
|
||||
}
|
||||
|
||||
@ -179,18 +180,14 @@ impl LoweredModule {
|
||||
self.add_use_item(source_map, it);
|
||||
}
|
||||
ast::ModuleItemKind::ExternCrateItem(it) => {
|
||||
// Lower `extern crate x` to `use ::x`. This is kind of cheating
|
||||
// and only works if we always interpret absolute paths in the
|
||||
// 2018 style; otherwise `::x` could also refer to a module in
|
||||
// the crate root.
|
||||
if let Some(name_ref) = it.name_ref() {
|
||||
let mut path = Path::from_name_ref(name_ref);
|
||||
path.kind = PathKind::Abs;
|
||||
let path = Path::from_name_ref(name_ref);
|
||||
let alias = it.alias().and_then(|a| a.name()).map(AsName::as_name);
|
||||
self.imports.alloc(ImportData {
|
||||
path,
|
||||
alias,
|
||||
is_glob: false,
|
||||
is_prelude: false,
|
||||
is_extern_crate: true,
|
||||
});
|
||||
}
|
||||
@ -214,11 +211,14 @@ impl LoweredModule {
|
||||
}
|
||||
|
||||
fn add_use_item(&mut self, source_map: &mut ImportSourceMap, item: &ast::UseItem) {
|
||||
let is_prelude =
|
||||
item.attrs().any(|attr| attr.as_atom().map(|s| s == "prelude_import").unwrap_or(false));
|
||||
Path::expand_use_item(item, |path, segment, alias| {
|
||||
let import = self.imports.alloc(ImportData {
|
||||
path,
|
||||
alias,
|
||||
is_glob: segment.is_none(),
|
||||
is_prelude,
|
||||
is_extern_crate: false,
|
||||
});
|
||||
if let Some(segment) = segment {
|
||||
|
@ -265,6 +265,45 @@ fn glob_across_crates() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edition_2015_imports() {
|
||||
let mut db = MockDatabase::with_files(
|
||||
"
|
||||
//- /main.rs
|
||||
mod foo;
|
||||
mod bar;
|
||||
|
||||
//- /bar.rs
|
||||
struct Bar;
|
||||
|
||||
//- /foo.rs
|
||||
use bar::Bar;
|
||||
use other_crate::FromLib;
|
||||
|
||||
//- /lib.rs
|
||||
struct FromLib;
|
||||
",
|
||||
);
|
||||
db.set_crate_graph_from_fixture(crate_graph! {
|
||||
"main": ("/main.rs", "2015", ["other_crate"]),
|
||||
"other_crate": ("/lib.rs", "2018", []),
|
||||
});
|
||||
let foo_id = db.file_id_of("/foo.rs");
|
||||
|
||||
let module = crate::source_binder::module_from_file_id(&db, foo_id).unwrap();
|
||||
let krate = module.krate(&db).unwrap();
|
||||
let item_map = db.item_map(krate);
|
||||
|
||||
check_module_item_map(
|
||||
&item_map,
|
||||
module.module_id,
|
||||
"
|
||||
Bar: t v
|
||||
FromLib: t v
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_resolution_works_for_non_standard_filenames() {
|
||||
let mut db = MockDatabase::with_files(
|
||||
@ -296,6 +335,43 @@ fn module_resolution_works_for_non_standard_filenames() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn std_prelude() {
|
||||
covers!(std_prelude);
|
||||
let mut db = MockDatabase::with_files(
|
||||
"
|
||||
//- /main.rs
|
||||
use Foo::*;
|
||||
|
||||
//- /lib.rs
|
||||
mod prelude;
|
||||
#[prelude_import]
|
||||
use prelude::*;
|
||||
|
||||
//- /prelude.rs
|
||||
pub enum Foo { Bar, Baz };
|
||||
",
|
||||
);
|
||||
db.set_crate_graph_from_fixture(crate_graph! {
|
||||
"main": ("/main.rs", ["test_crate"]),
|
||||
"test_crate": ("/lib.rs", []),
|
||||
});
|
||||
let main_id = db.file_id_of("/main.rs");
|
||||
|
||||
let module = crate::source_binder::module_from_file_id(&db, main_id).unwrap();
|
||||
let krate = module.krate(&db).unwrap();
|
||||
let item_map = db.item_map(krate);
|
||||
|
||||
check_module_item_map(
|
||||
&item_map,
|
||||
module.module_id,
|
||||
"
|
||||
Bar: t v
|
||||
Baz: t v
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_res_works_for_broken_modules() {
|
||||
covers!(name_res_works_for_broken_modules);
|
||||
@ -466,6 +542,42 @@ fn extern_crate_rename() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extern_crate_rename_2015_edition() {
|
||||
let mut db = MockDatabase::with_files(
|
||||
"
|
||||
//- /main.rs
|
||||
extern crate alloc as alloc_crate;
|
||||
|
||||
mod alloc;
|
||||
mod sync;
|
||||
|
||||
//- /sync.rs
|
||||
use alloc_crate::Arc;
|
||||
|
||||
//- /lib.rs
|
||||
struct Arc;
|
||||
",
|
||||
);
|
||||
db.set_crate_graph_from_fixture(crate_graph! {
|
||||
"main": ("/main.rs", "2015", ["alloc"]),
|
||||
"alloc": ("/lib.rs", []),
|
||||
});
|
||||
let sync_id = db.file_id_of("/sync.rs");
|
||||
|
||||
let module = crate::source_binder::module_from_file_id(&db, sync_id).unwrap();
|
||||
let krate = module.krate(&db).unwrap();
|
||||
let item_map = db.item_map(krate);
|
||||
|
||||
check_module_item_map(
|
||||
&item_map,
|
||||
module.module_id,
|
||||
"
|
||||
Arc: t v
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_across_source_roots() {
|
||||
let mut db = MockDatabase::with_files(
|
||||
|
@ -56,10 +56,10 @@ pub enum Resolution {
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
pub fn resolve_name(&self, name: &Name) -> PerNs<Resolution> {
|
||||
pub fn resolve_name(&self, db: &impl HirDatabase, name: &Name) -> PerNs<Resolution> {
|
||||
let mut resolution = PerNs::none();
|
||||
for scope in self.scopes.iter().rev() {
|
||||
resolution = resolution.or(scope.resolve_name(name));
|
||||
resolution = resolution.or(scope.resolve_name(db, name));
|
||||
if resolution.is_both() {
|
||||
return resolution;
|
||||
}
|
||||
@ -69,9 +69,9 @@ impl Resolver {
|
||||
|
||||
pub fn resolve_path(&self, db: &impl HirDatabase, path: &Path) -> PerNs<Resolution> {
|
||||
if let Some(name) = path.as_ident() {
|
||||
self.resolve_name(name)
|
||||
self.resolve_name(db, name)
|
||||
} else if path.is_self() {
|
||||
self.resolve_name(&Name::self_param())
|
||||
self.resolve_name(db, &Name::self_param())
|
||||
} else {
|
||||
let (item_map, module) = match self.module() {
|
||||
Some(m) => m,
|
||||
@ -82,10 +82,10 @@ impl Resolver {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_names(&self) -> FxHashMap<Name, PerNs<Resolution>> {
|
||||
pub fn all_names(&self, db: &impl HirDatabase) -> FxHashMap<Name, PerNs<Resolution>> {
|
||||
let mut names = FxHashMap::default();
|
||||
for scope in self.scopes.iter().rev() {
|
||||
scope.collect_names(&mut |name, res| {
|
||||
scope.collect_names(db, &mut |name, res| {
|
||||
let current: &mut PerNs<Resolution> = names.entry(name).or_default();
|
||||
if current.types.is_none() {
|
||||
current.types = res.types;
|
||||
@ -143,13 +143,13 @@ impl Resolver {
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
fn resolve_name(&self, name: &Name) -> PerNs<Resolution> {
|
||||
fn resolve_name(&self, db: &impl HirDatabase, name: &Name) -> PerNs<Resolution> {
|
||||
match self {
|
||||
Scope::ModuleScope(m) => {
|
||||
if let Some(KnownName::SelfParam) = name.as_known_name() {
|
||||
PerNs::types(Resolution::Def(m.module.into()))
|
||||
} else {
|
||||
m.item_map.resolve_name_in_module(m.module, name).map(Resolution::Def)
|
||||
m.item_map.resolve_name_in_module(db, m.module, name).map(Resolution::Def)
|
||||
}
|
||||
}
|
||||
Scope::GenericParams(gp) => match gp.find_by_name(name) {
|
||||
@ -174,7 +174,7 @@ impl Scope {
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_names(&self, f: &mut dyn FnMut(Name, PerNs<Resolution>)) {
|
||||
fn collect_names(&self, db: &impl HirDatabase, f: &mut dyn FnMut(Name, PerNs<Resolution>)) {
|
||||
match self {
|
||||
Scope::ModuleScope(m) => {
|
||||
// TODO: should we provide `self` here?
|
||||
@ -190,6 +190,12 @@ impl Scope {
|
||||
m.item_map.extern_prelude.iter().for_each(|(name, def)| {
|
||||
f(name.clone(), PerNs::types(Resolution::Def(*def)));
|
||||
});
|
||||
if let Some(prelude) = m.item_map.prelude {
|
||||
let prelude_item_map = db.item_map(prelude.krate);
|
||||
prelude_item_map[prelude.module_id].entries().for_each(|(name, res)| {
|
||||
f(name.clone(), res.def.map(Resolution::Def));
|
||||
});
|
||||
}
|
||||
}
|
||||
Scope::GenericParams(gp) => {
|
||||
for param in &gp.params {
|
||||
|
@ -4,7 +4,7 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if !ctx.is_trivial_path {
|
||||
return;
|
||||
}
|
||||
let names = ctx.resolver.all_names();
|
||||
let names = ctx.resolver.all_names(ctx.db);
|
||||
|
||||
names.into_iter().for_each(|(name, res)| {
|
||||
CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string())
|
||||
@ -165,4 +165,23 @@ mod tests {
|
||||
fn completes_self_in_methods() {
|
||||
check_reference_completion("self_in_methods", r"impl S { fn foo(&self) { <|> } }")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_prelude() {
|
||||
check_reference_completion(
|
||||
"completes_prelude",
|
||||
"
|
||||
//- /main.rs
|
||||
fn foo() { let x: <|> }
|
||||
|
||||
//- /std/lib.rs
|
||||
#[prelude_import]
|
||||
use prelude::*;
|
||||
|
||||
mod prelude {
|
||||
struct Option;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
---
|
||||
created: "2019-02-13T19:52:43.734834624Z"
|
||||
creator: insta@0.6.2
|
||||
source: crates/ra_ide_api/src/completion/completion_item.rs
|
||||
expression: kind_completions
|
||||
---
|
||||
[
|
||||
CompletionItem {
|
||||
completion_kind: Reference,
|
||||
label: "Option",
|
||||
kind: Some(
|
||||
Struct
|
||||
),
|
||||
detail: None,
|
||||
documentation: None,
|
||||
lookup: None,
|
||||
insert_text: None,
|
||||
insert_text_format: PlainText,
|
||||
source_range: [18; 18),
|
||||
text_edit: None
|
||||
},
|
||||
CompletionItem {
|
||||
completion_kind: Reference,
|
||||
label: "foo",
|
||||
kind: Some(
|
||||
Function
|
||||
),
|
||||
detail: Some(
|
||||
"fn foo()"
|
||||
),
|
||||
documentation: None,
|
||||
lookup: None,
|
||||
insert_text: Some(
|
||||
"foo()$0"
|
||||
),
|
||||
insert_text_format: Snippet,
|
||||
source_range: [18; 18),
|
||||
text_edit: None
|
||||
},
|
||||
CompletionItem {
|
||||
completion_kind: Reference,
|
||||
label: "std",
|
||||
kind: Some(
|
||||
Module
|
||||
),
|
||||
detail: None,
|
||||
documentation: None,
|
||||
lookup: None,
|
||||
insert_text: None,
|
||||
insert_text_format: PlainText,
|
||||
source_range: [18; 18),
|
||||
text_edit: None
|
||||
}
|
||||
]
|
@ -62,7 +62,8 @@ pub use ra_ide_api_light::{
|
||||
LineIndex, LineCol, translate_offset_with_edit,
|
||||
};
|
||||
pub use ra_db::{
|
||||
Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId
|
||||
Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId,
|
||||
Edition
|
||||
};
|
||||
pub use hir::Documentation;
|
||||
|
||||
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use relative_path::RelativePathBuf;
|
||||
use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER};
|
||||
|
||||
use crate::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, FilePosition, FileRange, SourceRootId};
|
||||
use crate::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, FilePosition, FileRange, SourceRootId, Edition::Edition2018};
|
||||
|
||||
/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
|
||||
/// from a set of in-memory files.
|
||||
@ -89,9 +89,9 @@ impl MockAnalysis {
|
||||
let path = RelativePathBuf::from_path(&path[1..]).unwrap();
|
||||
let file_id = FileId(i as u32 + 1);
|
||||
if path == "/lib.rs" || path == "/main.rs" {
|
||||
root_crate = Some(crate_graph.add_crate_root(file_id));
|
||||
root_crate = Some(crate_graph.add_crate_root(file_id, Edition2018));
|
||||
} else if path.ends_with("/lib.rs") {
|
||||
let other_crate = crate_graph.add_crate_root(file_id);
|
||||
let other_crate = crate_graph.add_crate_root(file_id, Edition2018);
|
||||
let crate_name = path.parent().unwrap().file_name().unwrap();
|
||||
if let Some(root_crate) = root_crate {
|
||||
crate_graph.add_dep(root_crate, crate_name.into(), other_crate).unwrap();
|
||||
|
@ -1,7 +1,7 @@
|
||||
use insta::assert_debug_snapshot_matches;
|
||||
use ra_ide_api::{
|
||||
mock_analysis::{single_file, single_file_with_position, MockAnalysis},
|
||||
AnalysisChange, CrateGraph, FileId, Query, NavigationTarget,
|
||||
AnalysisChange, CrateGraph, Edition::Edition2018, FileId, Query, NavigationTarget
|
||||
};
|
||||
use ra_syntax::{TextRange, SmolStr};
|
||||
|
||||
@ -36,7 +36,7 @@ fn test_resolve_crate_root() {
|
||||
assert!(host.analysis().crate_for(mod_file).unwrap().is_empty());
|
||||
|
||||
let mut crate_graph = CrateGraph::default();
|
||||
let crate_id = crate_graph.add_crate_root(root_file);
|
||||
let crate_id = crate_graph.add_crate_root(root_file, Edition2018);
|
||||
let mut change = AnalysisChange::new();
|
||||
change.set_crate_graph(crate_graph);
|
||||
host.apply_change(change);
|
||||
|
@ -4,6 +4,7 @@ use cargo_metadata::{MetadataCommand, CargoOpt};
|
||||
use ra_arena::{Arena, RawId, impl_arena_id};
|
||||
use rustc_hash::FxHashMap;
|
||||
use failure::format_err;
|
||||
use ra_db::Edition;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
@ -35,6 +36,7 @@ struct PackageData {
|
||||
targets: Vec<Target>,
|
||||
is_member: bool,
|
||||
dependencies: Vec<PackageDependency>,
|
||||
edition: Edition,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -84,6 +86,9 @@ impl Package {
|
||||
pub fn root(self, ws: &CargoWorkspace) -> &Path {
|
||||
ws.packages[self].manifest.parent().unwrap()
|
||||
}
|
||||
pub fn edition(self, ws: &CargoWorkspace) -> Edition {
|
||||
ws.packages[self].edition
|
||||
}
|
||||
pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a {
|
||||
ws.packages[self].targets.iter().cloned()
|
||||
}
|
||||
@ -135,6 +140,7 @@ impl CargoWorkspace {
|
||||
manifest: meta_pkg.manifest_path.clone(),
|
||||
targets: Vec::new(),
|
||||
is_member,
|
||||
edition: Edition::from_string(&meta_pkg.edition),
|
||||
dependencies: Vec::new(),
|
||||
});
|
||||
let pkg_data = &mut packages[pkg];
|
||||
|
@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
|
||||
use failure::bail;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ra_db::{CrateGraph, FileId};
|
||||
use ra_db::{CrateGraph, FileId, Edition};
|
||||
|
||||
pub use crate::{
|
||||
cargo_workspace::{CargoWorkspace, Package, Target, TargetKind},
|
||||
@ -36,7 +36,8 @@ impl ProjectWorkspace {
|
||||
let mut sysroot_crates = FxHashMap::default();
|
||||
for krate in self.sysroot.crates() {
|
||||
if let Some(file_id) = load(krate.root(&self.sysroot)) {
|
||||
sysroot_crates.insert(krate, crate_graph.add_crate_root(file_id));
|
||||
sysroot_crates
|
||||
.insert(krate, crate_graph.add_crate_root(file_id, Edition::Edition2015));
|
||||
}
|
||||
}
|
||||
for from in self.sysroot.crates() {
|
||||
@ -62,7 +63,8 @@ impl ProjectWorkspace {
|
||||
for tgt in pkg.targets(&self.cargo) {
|
||||
let root = tgt.root(&self.cargo);
|
||||
if let Some(file_id) = load(root) {
|
||||
let crate_id = crate_graph.add_crate_root(file_id);
|
||||
let edition = pkg.edition(&self.cargo);
|
||||
let crate_id = crate_graph.add_crate_root(file_id, edition);
|
||||
if tgt.kind(&self.cargo) == TargetKind::Lib {
|
||||
lib_tgt = Some(crate_id);
|
||||
pkg_to_lib_crate.insert(pkg, crate_id);
|
||||
|
@ -4210,6 +4210,7 @@ impl ToOwned for UseItem {
|
||||
}
|
||||
|
||||
|
||||
impl ast::AttrsOwner for UseItem {}
|
||||
impl UseItem {
|
||||
pub fn use_tree(&self) -> Option<&UseTree> {
|
||||
super::child_opt(self)
|
||||
|
@ -596,7 +596,8 @@ Grammar(
|
||||
options: [ "Pat", "TypeRef" ],
|
||||
),
|
||||
"UseItem": (
|
||||
options: [ "UseTree" ]
|
||||
traits: ["AttrsOwner"],
|
||||
options: [ "UseTree" ],
|
||||
),
|
||||
"UseTree": (
|
||||
options: [ "Path", "UseTreeList", "Alias" ]
|
||||
|
Loading…
x
Reference in New Issue
Block a user