mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 06:11:35 +00:00
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:
commit
0199572a3d
5 changed files with 114 additions and 51 deletions
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -16,9 +16,16 @@ 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};
|
||||||
for nav in navs {
|
let ref_result = reference_definition(db, position.file_id, name_ref)?;
|
||||||
res.extend(doc_text_for(db, nav)?)
|
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 {
|
||||||
|
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());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue