517: gracefully handle cycles in crate graph r=matklad a=matklad



518: Add an explanatory message when we use the Query fallback r=matklad a=DJMcNab

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/269.

There is no good way to explain it for go_to_def, so I've just fallen back on to_vec.

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
Co-authored-by: DJMcNab <36049421+djmcnab@users.noreply.github.com>
This commit is contained in:
bors[bot] 2019-01-13 10:41:47 +00:00
commit 0199572a3d
5 changed files with 114 additions and 51 deletions

View file

@ -53,6 +53,9 @@ pub struct CrateGraph {
arena: FxHashMap<CrateId, CrateData>, arena: FxHashMap<CrateId, CrateData>,
} }
#[derive(Debug)]
pub struct CyclicDependencies;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CrateId(pub u32); pub struct CrateId(pub u32);
@ -94,12 +97,16 @@ impl CrateGraph {
assert!(prev.is_none()); assert!(prev.is_none());
crate_id crate_id
} }
pub fn add_dep(&mut self, from: CrateId, name: SmolStr, to: CrateId) { pub fn add_dep(
let mut visited = FxHashSet::default(); &mut self,
if self.dfs_find(from, to, &mut visited) { from: CrateId,
panic!("Cycle dependencies found.") name: SmolStr,
to: CrateId,
) -> Result<(), CyclicDependencies> {
if self.dfs_find(from, to, &mut FxHashSet::default()) {
return Err(CyclicDependencies);
} }
self.arena.get_mut(&from).unwrap().add_dep(name, to) Ok(self.arena.get_mut(&from).unwrap().add_dep(name, to))
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.arena.is_empty() self.arena.is_empty()
@ -139,35 +146,6 @@ impl CrateGraph {
} }
} }
#[cfg(test)]
mod tests {
use super::{CrateGraph, FxHashMap, FileId, SmolStr};
#[test]
#[should_panic]
fn it_should_painc_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));
graph.add_dep(crate1, SmolStr::new("crate2"), crate2);
graph.add_dep(crate2, SmolStr::new("crate3"), crate3);
graph.add_dep(crate3, SmolStr::new("crate1"), crate1);
}
#[test]
fn it_works() {
let mut graph = CrateGraph {
arena: FxHashMap::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));
graph.add_dep(crate1, SmolStr::new("crate2"), crate2);
graph.add_dep(crate2, SmolStr::new("crate3"), crate3);
}
}
salsa::query_group! { salsa::query_group! {
pub trait FilesDatabase: salsa::Database { pub trait FilesDatabase: salsa::Database {
/// Text of the file. /// Text of the file.
@ -209,3 +187,39 @@ salsa::query_group! {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::{CrateGraph, FileId, SmolStr};
#[test]
fn it_should_painc_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));
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());
}
#[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));
assert!(graph
.add_dep(crate1, SmolStr::new("crate2"), crate2)
.is_ok());
assert!(graph
.add_dep(crate2, SmolStr::new("crate3"), crate3)
.is_ok());
}
}

View file

@ -235,7 +235,9 @@ fn item_map_across_crates() {
let mut crate_graph = CrateGraph::default(); let mut crate_graph = CrateGraph::default();
let main_crate = crate_graph.add_crate_root(main_id); let main_crate = crate_graph.add_crate_root(main_id);
let lib_crate = crate_graph.add_crate_root(lib_id); let lib_crate = crate_graph.add_crate_root(lib_id);
crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate); crate_graph
.add_dep(main_crate, "test_crate".into(), lib_crate)
.unwrap();
db.set_crate_graph(crate_graph); db.set_crate_graph(crate_graph);
@ -288,7 +290,9 @@ fn import_across_source_roots() {
let mut crate_graph = CrateGraph::default(); let mut crate_graph = CrateGraph::default();
let main_crate = crate_graph.add_crate_root(main_id); let main_crate = crate_graph.add_crate_root(main_id);
let lib_crate = crate_graph.add_crate_root(lib_id); let lib_crate = crate_graph.add_crate_root(lib_id);
crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate); crate_graph
.add_dep(main_crate, "test_crate".into(), lib_crate)
.unwrap();
db.set_crate_graph(crate_graph); db.set_crate_graph(crate_graph);
@ -330,7 +334,9 @@ fn reexport_across_crates() {
let mut crate_graph = CrateGraph::default(); let mut crate_graph = CrateGraph::default();
let main_crate = crate_graph.add_crate_root(main_id); let main_crate = crate_graph.add_crate_root(main_id);
let lib_crate = crate_graph.add_crate_root(lib_id); let lib_crate = crate_graph.add_crate_root(lib_id);
crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate); crate_graph
.add_dep(main_crate, "test_crate".into(), lib_crate)
.unwrap();
db.set_crate_graph(crate_graph); db.set_crate_graph(crate_graph);

View file

@ -13,8 +13,11 @@ pub(crate) fn goto_definition(
let file = db.source_file(position.file_id); let file = db.source_file(position.file_id);
let syntax = file.syntax(); let syntax = file.syntax();
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) { if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) {
let navs = reference_definition(db, position.file_id, name_ref)?; let navs = reference_definition(db, position.file_id, name_ref)?.to_vec();
return Ok(Some(RangeInfo::new(name_ref.syntax().range(), navs))); return Ok(Some(RangeInfo::new(
name_ref.syntax().range(),
navs.to_vec(),
)));
} }
if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) { if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) {
let navs = ctry!(name_definition(db, position.file_id, name)?); let navs = ctry!(name_definition(db, position.file_id, name)?);
@ -23,11 +26,27 @@ pub(crate) fn goto_definition(
Ok(None) Ok(None)
} }
pub(crate) enum ReferenceResult {
Exact(NavigationTarget),
Approximate(Vec<NavigationTarget>),
}
impl ReferenceResult {
fn to_vec(self) -> Vec<NavigationTarget> {
use self::ReferenceResult::*;
match self {
Exact(target) => vec![target],
Approximate(vec) => vec,
}
}
}
pub(crate) fn reference_definition( pub(crate) fn reference_definition(
db: &RootDatabase, db: &RootDatabase,
file_id: FileId, file_id: FileId,
name_ref: &ast::NameRef, name_ref: &ast::NameRef,
) -> Cancelable<Vec<NavigationTarget>> { ) -> Cancelable<ReferenceResult> {
use self::ReferenceResult::*;
if let Some(fn_descr) = if let Some(fn_descr) =
hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())? hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())?
{ {
@ -35,7 +54,7 @@ pub(crate) fn reference_definition(
// First try to resolve the symbol locally // First try to resolve the symbol locally
if let Some(entry) = scope.resolve_local_name(name_ref) { if let Some(entry) = scope.resolve_local_name(name_ref) {
let nav = NavigationTarget::from_scope_entry(file_id, &entry); let nav = NavigationTarget::from_scope_entry(file_id, &entry);
return Ok(vec![nav]); return Ok(Exact(nav));
}; };
} }
// Then try module name resolution // Then try module name resolution
@ -51,7 +70,7 @@ pub(crate) fn reference_definition(
let resolved = module.resolve_path(db, &path)?; let resolved = module.resolve_path(db, &path)?;
if let Some(def_id) = resolved.take_types().or(resolved.take_values()) { if let Some(def_id) = resolved.take_types().or(resolved.take_values()) {
if let Some(target) = NavigationTarget::from_def(db, def_id.resolve(db)?)? { if let Some(target) = NavigationTarget::from_def(db, def_id.resolve(db)?)? {
return Ok(vec![target]); return Ok(Exact(target));
} }
} }
} }
@ -62,7 +81,7 @@ pub(crate) fn reference_definition(
.into_iter() .into_iter()
.map(NavigationTarget::from_symbol) .map(NavigationTarget::from_symbol)
.collect(); .collect();
Ok(navs) Ok(Approximate(navs))
} }
fn name_definition( fn name_definition(

View file

@ -16,10 +16,17 @@ pub(crate) fn hover(
let mut range = None; let mut range = None;
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
let navs = crate::goto_definition::reference_definition(db, position.file_id, name_ref)?; use crate::goto_definition::{ReferenceResult::*, reference_definition};
let ref_result = reference_definition(db, position.file_id, name_ref)?;
match ref_result {
Exact(nav) => res.extend(doc_text_for(db, nav)?),
Approximate(navs) => {
res.push("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support glob imports or traits. \nThese methods were found instead:".to_string());
for nav in navs { for nav in navs {
res.extend(doc_text_for(db, nav)?) res.extend(doc_text_for(db, nav)?)
} }
}
}
if !res.is_empty() { if !res.is_empty() {
range = Some(name_ref.syntax().range()) range = Some(name_ref.syntax().range())
} }
@ -34,7 +41,7 @@ pub(crate) fn hover(
file_id: position.file_id, file_id: position.file_id,
range: node.range(), range: node.range(),
}; };
res.extend(type_of(db, frange)?); res.extend(type_of(db, frange)?.map(Into::into));
range = Some(node.range()); range = Some(node.range());
}; };

View file

@ -73,7 +73,9 @@ impl ServerWorldState {
if let (Some(&from), Some(&to)) = if let (Some(&from), Some(&to)) =
(sysroot_crates.get(&from), sysroot_crates.get(&to)) (sysroot_crates.get(&from), sysroot_crates.get(&to))
{ {
crate_graph.add_dep(from, name.clone(), to); if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
log::error!("cyclic dependency between sysroot crates")
}
} }
} }
} }
@ -108,11 +110,20 @@ impl ServerWorldState {
for &from in pkg_crates.get(&pkg).into_iter().flatten() { for &from in pkg_crates.get(&pkg).into_iter().flatten() {
if let Some(to) = lib_tgt { if let Some(to) = lib_tgt {
if to != from { if to != from {
crate_graph.add_dep(from, pkg.name(&ws.cargo).into(), to); if let Err(_) =
crate_graph.add_dep(from, pkg.name(&ws.cargo).into(), to)
{
log::error!(
"cyclic dependency between targets of {}",
pkg.name(&ws.cargo)
)
}
} }
} }
if let Some(std) = libstd { if let Some(std) = libstd {
crate_graph.add_dep(from, "std".into(), std); if let Err(_) = crate_graph.add_dep(from, "std".into(), std) {
log::error!("cyclic dependency on std for {}", pkg.name(&ws.cargo))
}
} }
} }
} }
@ -123,7 +134,13 @@ impl ServerWorldState {
for dep in pkg.dependencies(&ws.cargo) { for dep in pkg.dependencies(&ws.cargo) {
if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
for &from in pkg_crates.get(&pkg).into_iter().flatten() { for &from in pkg_crates.get(&pkg).into_iter().flatten() {
crate_graph.add_dep(from, dep.name.clone(), to); if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) {
log::error!(
"cyclic dependency {} -> {}",
pkg.name(&ws.cargo),
dep.pkg.name(&ws.cargo)
)
}
} }
} }
} }