mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
start query-based modules
This commit is contained in:
parent
6ee4c287f9
commit
99d02fe583
7 changed files with 365 additions and 45 deletions
|
@ -11,6 +11,7 @@ parking_lot = "0.6.3"
|
||||||
once_cell = "0.1.5"
|
once_cell = "0.1.5"
|
||||||
rayon = "1.0.2"
|
rayon = "1.0.2"
|
||||||
fst = "0.3.1"
|
fst = "0.3.1"
|
||||||
|
im = "12.0.0"
|
||||||
libsyntax2 = { path = "../libsyntax2" }
|
libsyntax2 = { path = "../libsyntax2" }
|
||||||
libeditor = { path = "../libeditor" }
|
libeditor = { path = "../libeditor" }
|
||||||
|
|
||||||
|
|
121
crates/libanalysis/src/db.rs
Normal file
121
crates/libanalysis/src/db.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
use std::{
|
||||||
|
hash::Hash,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use libsyntax2::{File};
|
||||||
|
use im;
|
||||||
|
use {
|
||||||
|
FileId,
|
||||||
|
imp::{FileResolverImp},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Db {
|
||||||
|
file_resolver: FileResolverImp,
|
||||||
|
files: im::HashMap<FileId, Arc<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Db {
|
||||||
|
pub(crate) fn new() -> Db {
|
||||||
|
Db {
|
||||||
|
file_resolver: FileResolverImp::default(),
|
||||||
|
files: im::HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn change_file(&mut self, file_id: FileId, text: Option<String>) {
|
||||||
|
match text {
|
||||||
|
None => {
|
||||||
|
self.files.remove(&file_id);
|
||||||
|
}
|
||||||
|
Some(text) => {
|
||||||
|
self.files.insert(file_id, Arc::new(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn set_file_resolver(&mut self, file_resolver: FileResolverImp) {
|
||||||
|
self.file_resolver = file_resolver
|
||||||
|
}
|
||||||
|
pub(crate) fn query_ctx(&self) -> QueryCtx {
|
||||||
|
QueryCtx { db: self.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct QueryCtx {
|
||||||
|
db: Db
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryCtx {
|
||||||
|
pub(crate) fn get<Q: Get>(&self, params: &Q::Params) -> Q::Output {
|
||||||
|
Q::get(self, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Query {
|
||||||
|
const ID: u32;
|
||||||
|
type Params: Hash;
|
||||||
|
type Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Get: Query {
|
||||||
|
fn get(ctx: &QueryCtx, params: &Self::Params) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Eval> Get for T {
|
||||||
|
fn get(ctx: &QueryCtx, params: &Self::Params) -> Self::Output {
|
||||||
|
Self::eval(ctx, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Eval: Query {
|
||||||
|
fn eval(ctx: &QueryCtx, params: &Self::Params) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct DbFiles {
|
||||||
|
db: Db,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbFiles {
|
||||||
|
pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item=FileId> + 'a {
|
||||||
|
self.db.files.keys().cloned()
|
||||||
|
}
|
||||||
|
pub(crate) fn file_resolver(&self) -> FileResolverImp {
|
||||||
|
self.db.file_resolver.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum Files {}
|
||||||
|
impl Query for Files {
|
||||||
|
const ID: u32 = 1;
|
||||||
|
type Params = ();
|
||||||
|
type Output = DbFiles;
|
||||||
|
}
|
||||||
|
impl Get for Files {
|
||||||
|
fn get(ctx: &QueryCtx, _params: &()) -> DbFiles {
|
||||||
|
DbFiles { db: ctx.db.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FileText {}
|
||||||
|
impl Query for FileText {
|
||||||
|
const ID: u32 = 10;
|
||||||
|
type Params = FileId;
|
||||||
|
type Output = Arc<String>;
|
||||||
|
}
|
||||||
|
impl Get for FileText {
|
||||||
|
fn get(ctx: &QueryCtx, file_id: &FileId) -> Arc<String> {
|
||||||
|
ctx.db.files[file_id].clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum FileSyntax {}
|
||||||
|
impl Query for FileSyntax {
|
||||||
|
const ID: u32 = 20;
|
||||||
|
type Params = FileId;
|
||||||
|
type Output = File;
|
||||||
|
}
|
||||||
|
impl Eval for FileSyntax {
|
||||||
|
fn eval(ctx: &QueryCtx, file_id: &FileId) -> File {
|
||||||
|
let text = ctx.get::<FileText>(file_id);
|
||||||
|
File::parse(&text)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,12 +9,15 @@ extern crate rayon;
|
||||||
extern crate relative_path;
|
extern crate relative_path;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate crossbeam_channel;
|
extern crate crossbeam_channel;
|
||||||
|
extern crate im;
|
||||||
|
|
||||||
mod symbol_index;
|
mod symbol_index;
|
||||||
mod module_map;
|
mod module_map;
|
||||||
|
mod module_map_db;
|
||||||
mod imp;
|
mod imp;
|
||||||
mod job;
|
mod job;
|
||||||
mod roots;
|
mod roots;
|
||||||
|
mod db;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
|
|
@ -244,19 +244,26 @@ impl Link {
|
||||||
self.points_to = Vec::new();
|
self.points_to = Vec::new();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let (points_to, problem) = resolve_submodule(self.owner.0, &self.name(), file_resolver);
|
||||||
|
self.problem = problem;
|
||||||
|
self.points_to = points_to.into_iter().map(ModuleId).collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mod_name = file_resolver.file_stem(self.owner.0);
|
pub(crate) fn resolve_submodule(file_id: FileId, name: &SmolStr, file_resolver: &FileResolverImp) -> (Vec<FileId>, Option<Problem>) {
|
||||||
|
let mod_name = file_resolver.file_stem(file_id);
|
||||||
let is_dir_owner =
|
let is_dir_owner =
|
||||||
mod_name == "mod" || mod_name == "lib" || mod_name == "main";
|
mod_name == "mod" || mod_name == "lib" || mod_name == "main";
|
||||||
|
|
||||||
let file_mod = RelativePathBuf::from(format!("../{}.rs", self.name()));
|
let file_mod = RelativePathBuf::from(format!("../{}.rs", name));
|
||||||
let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", self.name()));
|
let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", name));
|
||||||
|
let points_to: Vec<FileId>;
|
||||||
|
let problem: Option<Problem>;
|
||||||
if is_dir_owner {
|
if is_dir_owner {
|
||||||
self.points_to = [&file_mod, &dir_mod].iter()
|
points_to = [&file_mod, &dir_mod].iter()
|
||||||
.filter_map(|path| file_resolver.resolve(self.owner.0, path))
|
.filter_map(|path| file_resolver.resolve(file_id, path))
|
||||||
.map(ModuleId)
|
|
||||||
.collect();
|
.collect();
|
||||||
self.problem = if self.points_to.is_empty() {
|
problem = if points_to.is_empty() {
|
||||||
Some(Problem::UnresolvedModule {
|
Some(Problem::UnresolvedModule {
|
||||||
candidate: file_mod,
|
candidate: file_mod,
|
||||||
})
|
})
|
||||||
|
@ -264,11 +271,11 @@ impl Link {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.points_to = Vec::new();
|
points_to = Vec::new();
|
||||||
self.problem = Some(Problem::NotDirOwner {
|
problem = Some(Problem::NotDirOwner {
|
||||||
move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)),
|
move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)),
|
||||||
candidate: file_mod,
|
candidate: file_mod,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
(points_to, problem)
|
||||||
}
|
}
|
||||||
|
|
189
crates/libanalysis/src/module_map_db.rs
Normal file
189
crates/libanalysis/src/module_map_db.rs
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use {
|
||||||
|
FileId,
|
||||||
|
db::{Query, Eval, QueryCtx, FileSyntax, Files},
|
||||||
|
module_map::resolve_submodule,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ModuleDescr {}
|
||||||
|
impl Query for ModuleDescr {
|
||||||
|
const ID: u32 = 30;
|
||||||
|
type Params = FileId;
|
||||||
|
type Output = Arc<descr::ModuleDescr>;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ResolveSubmodule {}
|
||||||
|
impl Query for ResolveSubmodule {
|
||||||
|
const ID: u32 = 31;
|
||||||
|
type Params = (FileId, descr::Submodule);
|
||||||
|
type Output = Arc<Vec<FileId>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ParentModule {}
|
||||||
|
impl Query for ParentModule {
|
||||||
|
const ID: u32 = 40;
|
||||||
|
type Params = FileId;
|
||||||
|
type Output = Arc<Vec<FileId>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eval for ModuleDescr {
|
||||||
|
fn eval(ctx: &QueryCtx, file_id: &FileId) -> Arc<descr::ModuleDescr> {
|
||||||
|
let file = ctx.get::<FileSyntax>(file_id);
|
||||||
|
Arc::new(descr::ModuleDescr::new(file.ast()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eval for ResolveSubmodule {
|
||||||
|
fn eval(ctx: &QueryCtx, &(file_id, ref submodule): &(FileId, descr::Submodule)) -> Arc<Vec<FileId>> {
|
||||||
|
let files = ctx.get::<Files>(&());
|
||||||
|
let res = resolve_submodule(file_id, &submodule.name, &files.file_resolver()).0;
|
||||||
|
Arc::new(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eval for ParentModule {
|
||||||
|
fn eval(ctx: &QueryCtx, file_id: &FileId) -> Arc<Vec<FileId>> {
|
||||||
|
let files = ctx.get::<Files>(&());
|
||||||
|
let res = files.iter()
|
||||||
|
.map(|parent_id| (parent_id, ctx.get::<ModuleDescr>(&parent_id)))
|
||||||
|
.filter(|(parent_id, descr)| {
|
||||||
|
descr.submodules.iter()
|
||||||
|
.any(|subm| {
|
||||||
|
ctx.get::<ResolveSubmodule>(&(*parent_id, subm.clone()))
|
||||||
|
.iter()
|
||||||
|
.any(|it| it == file_id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|(id, _)| id)
|
||||||
|
.collect();
|
||||||
|
Arc::new(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod descr {
|
||||||
|
use libsyntax2::{
|
||||||
|
SmolStr,
|
||||||
|
ast::{self, NameOwner},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ModuleDescr {
|
||||||
|
pub submodules: Vec<Submodule>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleDescr {
|
||||||
|
pub fn new(root: ast::Root) -> ModuleDescr {
|
||||||
|
let submodules = root
|
||||||
|
.modules()
|
||||||
|
.filter_map(|module| {
|
||||||
|
let name = module.name()?.text();
|
||||||
|
if !module.has_semi() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Submodule { name })
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
ModuleDescr { submodules } }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Hash)]
|
||||||
|
pub struct Submodule {
|
||||||
|
pub name: SmolStr,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use im;
|
||||||
|
use relative_path::{RelativePath, RelativePathBuf};
|
||||||
|
use {
|
||||||
|
db::Db,
|
||||||
|
imp::FileResolverImp,
|
||||||
|
FileId, FileResolver,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FileMap(im::HashMap<FileId, RelativePathBuf>);
|
||||||
|
|
||||||
|
impl FileResolver for FileMap {
|
||||||
|
fn file_stem(&self, file_id: FileId) -> String {
|
||||||
|
self.0[&file_id].file_stem().unwrap().to_string()
|
||||||
|
}
|
||||||
|
fn resolve(&self, file_id: FileId, rel: &RelativePath) -> Option<FileId> {
|
||||||
|
let path = self.0[&file_id].join(rel).normalize();
|
||||||
|
self.0.iter()
|
||||||
|
.filter_map(|&(id, ref p)| Some(id).filter(|_| p == &path))
|
||||||
|
.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Fixture {
|
||||||
|
next_file_id: u32,
|
||||||
|
fm: im::HashMap<FileId, RelativePathBuf>,
|
||||||
|
db: Db,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fixture {
|
||||||
|
fn new() -> Fixture {
|
||||||
|
Fixture {
|
||||||
|
next_file_id: 1,
|
||||||
|
fm: im::HashMap::new(),
|
||||||
|
db: Db::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn add_file(&mut self, path: &str, text: &str) -> FileId {
|
||||||
|
assert!(path.starts_with("/"));
|
||||||
|
let file_id = FileId(self.next_file_id);
|
||||||
|
self.next_file_id += 1;
|
||||||
|
self.fm.insert(file_id, RelativePathBuf::from(&path[1..]));
|
||||||
|
self.db.change_file(file_id, Some(text.to_string()));
|
||||||
|
self.db.set_file_resolver(FileResolverImp::new(
|
||||||
|
Arc::new(FileMap(self.fm.clone()))
|
||||||
|
));
|
||||||
|
|
||||||
|
file_id
|
||||||
|
}
|
||||||
|
fn remove_file(&mut self, file_id: FileId) {
|
||||||
|
self.fm.remove(&file_id);
|
||||||
|
self.db.change_file(file_id, None);
|
||||||
|
self.db.set_file_resolver(FileResolverImp::new(
|
||||||
|
Arc::new(FileMap(self.fm.clone()))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn change_file(&mut self, file_id: FileId, new_text: &str) {
|
||||||
|
self.db.change_file(file_id, Some(new_text.to_string()));
|
||||||
|
}
|
||||||
|
fn check_parent_modules(&self, file_id: FileId, expected: &[FileId]) {
|
||||||
|
let ctx = self.db.query_ctx();
|
||||||
|
let actual = ctx.get::<ParentModule>(&file_id);
|
||||||
|
assert_eq!(actual.as_slice(), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parent_module() {
|
||||||
|
let mut f = Fixture::new();
|
||||||
|
let foo = f.add_file("/foo.rs", "");
|
||||||
|
f.check_parent_modules(foo, &[]);
|
||||||
|
|
||||||
|
let lib = f.add_file("/lib.rs", "mod foo;");
|
||||||
|
f.check_parent_modules(foo, &[lib]);
|
||||||
|
|
||||||
|
f.change_file(lib, "");
|
||||||
|
f.check_parent_modules(foo, &[]);
|
||||||
|
|
||||||
|
f.change_file(lib, "mod foo;");
|
||||||
|
f.check_parent_modules(foo, &[lib]);
|
||||||
|
|
||||||
|
f.change_file(lib, "mod bar;");
|
||||||
|
f.check_parent_modules(foo, &[]);
|
||||||
|
|
||||||
|
f.change_file(lib, "mod foo;");
|
||||||
|
f.check_parent_modules(foo, &[lib]);
|
||||||
|
|
||||||
|
f.remove_file(lib);
|
||||||
|
f.check_parent_modules(foo, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,24 +14,6 @@ use test_utils::assert_eq_dbg;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct FileMap(Vec<(FileId, RelativePathBuf)>);
|
struct FileMap(Vec<(FileId, RelativePathBuf)>);
|
||||||
|
|
||||||
fn analysis_host(files: &'static [(&'static str, &'static str)]) -> AnalysisHost {
|
|
||||||
let mut host = AnalysisHost::new();
|
|
||||||
let mut file_map = Vec::new();
|
|
||||||
for (id, &(path, contents)) in files.iter().enumerate() {
|
|
||||||
let file_id = FileId((id + 1) as u32);
|
|
||||||
assert!(path.starts_with('/'));
|
|
||||||
let path = RelativePathBuf::from_path(&path[1..]).unwrap();
|
|
||||||
host.change_file(file_id, Some(contents.to_string()));
|
|
||||||
file_map.push((file_id, path));
|
|
||||||
}
|
|
||||||
host.set_file_resolver(Arc::new(FileMap(file_map)));
|
|
||||||
host
|
|
||||||
}
|
|
||||||
|
|
||||||
fn analysis(files: &'static [(&'static str, &'static str)]) -> Analysis {
|
|
||||||
analysis_host(files).analysis()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileMap {
|
impl FileMap {
|
||||||
fn iter<'a>(&'a self) -> impl Iterator<Item=(FileId, &'a RelativePath)> + 'a {
|
fn iter<'a>(&'a self) -> impl Iterator<Item=(FileId, &'a RelativePath)> + 'a {
|
||||||
self.0.iter().map(|(id, path)| (*id, path.as_relative_path()))
|
self.0.iter().map(|(id, path)| (*id, path.as_relative_path()))
|
||||||
|
@ -56,6 +38,23 @@ impl FileResolver for FileMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn analysis_host(files: &'static [(&'static str, &'static str)]) -> AnalysisHost {
|
||||||
|
let mut host = AnalysisHost::new();
|
||||||
|
let mut file_map = Vec::new();
|
||||||
|
for (id, &(path, contents)) in files.iter().enumerate() {
|
||||||
|
let file_id = FileId((id + 1) as u32);
|
||||||
|
assert!(path.starts_with('/'));
|
||||||
|
let path = RelativePathBuf::from_path(&path[1..]).unwrap();
|
||||||
|
host.change_file(file_id, Some(contents.to_string()));
|
||||||
|
file_map.push((file_id, path));
|
||||||
|
}
|
||||||
|
host.set_file_resolver(Arc::new(FileMap(file_map)));
|
||||||
|
host
|
||||||
|
}
|
||||||
|
|
||||||
|
fn analysis(files: &'static [(&'static str, &'static str)]) -> Analysis {
|
||||||
|
analysis_host(files).analysis()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resolve_module() {
|
fn test_resolve_module() {
|
||||||
|
|
|
@ -17,7 +17,7 @@ log = "0.4.3"
|
||||||
url_serde = "0.2.0"
|
url_serde = "0.2.0"
|
||||||
languageserver-types = "0.49.0"
|
languageserver-types = "0.49.0"
|
||||||
walkdir = "2.2.0"
|
walkdir = "2.2.0"
|
||||||
im = { version = "11.0.1", features = ["arc"] }
|
im = "12.0.0"
|
||||||
cargo_metadata = "0.6.0"
|
cargo_metadata = "0.6.0"
|
||||||
text_unit = { version = "0.1.2", features = ["serde"] }
|
text_unit = { version = "0.1.2", features = ["serde"] }
|
||||||
smol_str = { version = "0.1.5", features = ["serde"] }
|
smol_str = { version = "0.1.5", features = ["serde"] }
|
||||||
|
|
Loading…
Reference in a new issue