Salsify the crate graph

I.e. make it not one giant input but multiple, for incrementality and decreased memory usage for Salsa 3 reasons.
This commit is contained in:
Chayim Refael Friedman 2025-01-02 01:45:32 +02:00
parent 44f18c3d05
commit c94e9efbef
108 changed files with 3630 additions and 2512 deletions

View file

@ -504,9 +504,7 @@ fn get_doc_base_urls(
let Some(krate) = krate else { return Default::default() };
let Some(display_name) = krate.display_name(db) else { return Default::default() };
let crate_data = &db.crate_graph()[krate.into()];
let (web_base, local_base) = match &crate_data.origin {
let (web_base, local_base) = match krate.origin(db) {
// std and co do not specify `html_root_url` any longer so we gotta handwrite this ourself.
// FIXME: Use the toolchains channel instead of nightly
CrateOrigin::Lang(

View file

@ -1,8 +1,8 @@
use hir::db::ExpandDatabase;
use hir::{ExpandResult, InFile, MacroFileIdExt, Semantics};
use ide_db::base_db::CrateId;
use ide_db::{
helpers::pick_best_token, syntax_helpers::prettify_macro_expansion, FileId, RootDatabase,
base_db::Crate, helpers::pick_best_token, syntax_helpers::prettify_macro_expansion, FileId,
RootDatabase,
};
use span::{Edition, SpanMap, SyntaxContextId, TextRange, TextSize};
use stdx::format_to;
@ -208,7 +208,7 @@ fn format(
file_id: FileId,
expanded: SyntaxNode,
span_map: &SpanMap<SyntaxContextId>,
krate: CrateId,
krate: Crate,
) -> String {
let expansion = prettify_macro_expansion(db, expanded, span_map, krate).to_string();
@ -249,7 +249,7 @@ fn _format(
let upcast_db = ide_db::base_db::Upcast::<dyn ide_db::base_db::RootQueryDb>::upcast(db);
let &crate_id = upcast_db.relevant_crates(file_id).iter().next()?;
let edition = upcast_db.crate_graph()[crate_id].edition;
let edition = crate_id.data(upcast_db).edition;
#[allow(clippy::disallowed_methods)]
let mut cmd = std::process::Command::new(toolchain::Tool::Rustfmt.path());

View file

@ -20,21 +20,24 @@ pub struct CrateInfo {
//
// ![Show Dependency Tree](https://user-images.githubusercontent.com/5748995/229394139-2625beab-f4c9-484b-84ed-ad5dee0b1e1a.png)
pub(crate) fn fetch_crates(db: &RootDatabase) -> FxIndexSet<CrateInfo> {
let crate_graph = db.crate_graph();
crate_graph
db.all_crates()
.iter()
.map(|crate_id| &crate_graph[crate_id])
.filter(|&data| !matches!(data.origin, CrateOrigin::Local { .. }))
.map(crate_info)
.copied()
.map(|crate_id| (crate_id.data(db), crate_id.extra_data(db)))
.filter(|(data, _)| !matches!(data.origin, CrateOrigin::Local { .. }))
.map(|(data, extra_data)| crate_info(data, extra_data))
.collect()
}
fn crate_info(data: &ide_db::base_db::CrateData) -> CrateInfo {
let crate_name = crate_name(data);
let version = data.version.clone();
fn crate_info(
data: &ide_db::base_db::BuiltCrateData,
extra_data: &ide_db::base_db::ExtraCrateData,
) -> CrateInfo {
let crate_name = crate_name(extra_data);
let version = extra_data.version.clone();
CrateInfo { name: crate_name, version, root_file_id: data.root_file_id }
}
fn crate_name(data: &ide_db::base_db::CrateData) -> Option<String> {
fn crate_name(data: &ide_db::base_db::ExtraCrateData) -> Option<String> {
data.display_name.as_ref().map(|it| it.canonical_name().as_str().to_owned())
}

View file

@ -8,7 +8,6 @@ use hir::{
MethodViolationCode, Name, Semantics, Symbol, Trait, Type, TypeInfo, VariantDef,
};
use ide_db::{
base_db::RootQueryDb,
defs::Definition,
documentation::HasDocs,
famous_defs::FamousDefs,
@ -466,8 +465,7 @@ pub(super) fn path(
item_name: Option<String>,
edition: Edition,
) -> String {
let crate_name =
db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string());
let crate_name = module.krate().display_name(db).as_ref().map(|it| it.to_string());
let module_path = module
.path_to_root(db)
.into_iter()

View file

@ -9252,7 +9252,7 @@ fn main() {
S
```
___
Implements notable traits: Notable, Future<Output = u32>, Iterator<Item = S>"#]],
Implements notable traits: Future<Output = u32>, Iterator<Item = S>, Notable"#]],
);
}

View file

@ -868,15 +868,15 @@ fn main() {
//- minicore: fn
fn main() {
let x = || 2;
//^ {closure#26624}
//^ {closure#25600}
let y = |t: i32| x() + t;
//^ {closure#26625}
//^ {closure#25601}
let mut t = 5;
//^ i32
let z = |k: i32| { t += k; };
//^ {closure#26626}
//^ {closure#25602}
let p = (y, z);
//^ ({closure#26625}, {closure#26626})
//^ ({closure#25601}, {closure#25602})
}
"#,
);

View file

@ -57,7 +57,7 @@ mod view_memory_layout;
mod view_mir;
mod view_syntax_tree;
use std::{iter, panic::UnwindSafe};
use std::panic::UnwindSafe;
use cfg::CfgOptions;
use fetch_crates::CrateInfo;
@ -125,7 +125,7 @@ pub use ide_completion::{
};
pub use ide_db::text_edit::{Indel, TextEdit};
pub use ide_db::{
base_db::{CrateGraph, CrateId, FileChange, SourceRoot, SourceRootId},
base_db::{Crate, CrateGraphBuilder, FileChange, SourceRoot, SourceRootId},
documentation::Documentation,
label::Label,
line_index::{LineCol, LineIndex},
@ -239,7 +239,7 @@ impl Analysis {
let mut change = ChangeWithProcMacros::new();
change.set_roots(vec![source_root]);
let mut crate_graph = CrateGraph::default();
let mut crate_graph = CrateGraphBuilder::default();
// FIXME: cfg options
// Default to enable test for single file.
let mut cfg_options = CfgOptions::default();
@ -255,16 +255,13 @@ impl Analysis {
CrateOrigin::Local { repo: None, name: None },
false,
None,
);
change.change_file(file_id, Some(text));
let ws_data = crate_graph
.iter()
.zip(iter::repeat(Arc::new(CrateWorkspaceData {
Arc::new(CrateWorkspaceData {
data_layout: Err("fixture has no layout".into()),
toolchain: None,
})))
.collect();
change.set_crate_graph(crate_graph, ws_data);
}),
);
change.change_file(file_id, Some(text));
change.set_crate_graph(crate_graph);
host.apply_change(change);
(host.analysis(), file_id)
@ -372,7 +369,7 @@ impl Analysis {
self.with_db(|db| test_explorer::discover_tests_in_crate_by_test_id(db, crate_id))
}
pub fn discover_tests_in_crate(&self, crate_id: CrateId) -> Cancellable<Vec<TestItem>> {
pub fn discover_tests_in_crate(&self, crate_id: Crate) -> Cancellable<Vec<TestItem>> {
self.with_db(|db| test_explorer::discover_tests_in_crate(db, crate_id))
}
@ -602,17 +599,17 @@ impl Analysis {
}
/// Returns crates that this file belongs to.
pub fn crates_for(&self, file_id: FileId) -> Cancellable<Vec<CrateId>> {
pub fn crates_for(&self, file_id: FileId) -> Cancellable<Vec<Crate>> {
self.with_db(|db| parent_module::crates_for(db, file_id))
}
/// Returns crates that this file belongs to.
pub fn transitive_rev_deps(&self, crate_id: CrateId) -> Cancellable<Vec<CrateId>> {
self.with_db(|db| db.crate_graph().transitive_rev_deps(crate_id).collect())
pub fn transitive_rev_deps(&self, crate_id: Crate) -> Cancellable<Vec<Crate>> {
self.with_db(|db| Vec::from_iter(db.transitive_rev_deps(crate_id)))
}
/// Returns crates that this file *might* belong to.
pub fn relevant_crates_for(&self, file_id: FileId) -> Cancellable<Vec<CrateId>> {
pub fn relevant_crates_for(&self, file_id: FileId) -> Cancellable<Vec<Crate>> {
self.with_db(|db| {
let db = Upcast::<dyn RootQueryDb>::upcast(db);
db.relevant_crates(file_id).iter().copied().collect()
@ -620,18 +617,23 @@ impl Analysis {
}
/// Returns the edition of the given crate.
pub fn crate_edition(&self, crate_id: CrateId) -> Cancellable<Edition> {
self.with_db(|db| db.crate_graph()[crate_id].edition)
pub fn crate_edition(&self, crate_id: Crate) -> Cancellable<Edition> {
self.with_db(|db| crate_id.data(db).edition)
}
/// Returns whether the given crate is a proc macro.
pub fn is_proc_macro_crate(&self, crate_id: Crate) -> Cancellable<bool> {
self.with_db(|db| crate_id.data(db).is_proc_macro)
}
/// Returns true if this crate has `no_std` or `no_core` specified.
pub fn is_crate_no_std(&self, crate_id: CrateId) -> Cancellable<bool> {
pub fn is_crate_no_std(&self, crate_id: Crate) -> Cancellable<bool> {
self.with_db(|db| hir::db::DefDatabase::crate_def_map(db, crate_id).is_no_std())
}
/// Returns the root file of the given crate.
pub fn crate_root(&self, crate_id: CrateId) -> Cancellable<FileId> {
self.with_db(|db| db.crate_graph()[crate_id].root_file_id)
pub fn crate_root(&self, crate_id: Crate) -> Cancellable<FileId> {
self.with_db(|db| crate_id.data(db).root_file_id)
}
/// Returns the set of possible targets to run for the current file.

View file

@ -1,6 +1,6 @@
use hir::{db::DefDatabase, Semantics};
use ide_db::{
base_db::{CrateId, RootQueryDb, Upcast},
base_db::{Crate, RootQueryDb, Upcast},
FileId, FilePosition, RootDatabase,
};
use itertools::Itertools;
@ -53,7 +53,7 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<Na
}
/// This returns `Vec` because a module may be included from several places.
pub(crate) fn crates_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
pub(crate) fn crates_for(db: &RootDatabase, file_id: FileId) -> Vec<Crate> {
let root_db = Upcast::<dyn RootQueryDb>::upcast(db);
root_db
.relevant_crates(file_id)

View file

@ -498,9 +498,8 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> {
};
let krate = def.krate(db);
let edition = krate.map(|it| it.edition(db)).unwrap_or(Edition::CURRENT);
let display_target = krate
.unwrap_or_else(|| (*db.crate_graph().crates_in_topological_order().last().unwrap()).into())
.to_display_target(db);
let display_target =
krate.unwrap_or_else(|| (*db.all_crates().last().unwrap()).into()).to_display_target(db);
if !has_runnable_doc_test(&attrs) {
return None;
}

View file

@ -119,9 +119,7 @@ fn documentation_for_definition(
sema.db,
famous_defs.as_ref(),
def.krate(sema.db)
.unwrap_or_else(|| {
(*sema.db.crate_graph().crates_in_topological_order().last().unwrap()).into()
})
.unwrap_or_else(|| (*sema.db.all_crates().last().unwrap()).into())
.to_display_target(sema.db),
)
}

View file

@ -1,4 +1,4 @@
use ide_db::base_db::{CrateData, RootQueryDb, Upcast};
use ide_db::base_db::{BuiltCrateData, ExtraCrateData};
use ide_db::RootDatabase;
use itertools::Itertools;
use span::FileId;
@ -34,28 +34,25 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
if crates.is_empty() {
format_to!(buf, "Does not belong to any crate");
}
let crate_graph = Upcast::<dyn RootQueryDb>::upcast(db).crate_graph();
for crate_id in crates {
let CrateData {
let BuiltCrateData {
root_file_id,
edition,
version,
display_name,
cfg_options,
potential_cfg_options,
env,
dependencies,
origin,
is_proc_macro,
proc_macro_cwd,
} = &crate_graph[crate_id];
} = crate_id.data(db);
let ExtraCrateData { version, display_name, potential_cfg_options } =
crate_id.extra_data(db);
let cfg_options = crate_id.cfg_options(db);
let env = crate_id.env(db);
format_to!(
buf,
"Crate: {}\n",
match display_name {
Some(it) => format!("{it}({})", crate_id.into_raw()),
None => format!("{}", crate_id.into_raw()),
Some(it) => format!("{it}({:?})", crate_id),
None => format!("{:?}", crate_id),
}
);
format_to!(buf, " Root module file id: {}\n", root_file_id.index());
@ -69,7 +66,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
format_to!(buf, " Proc macro cwd: {:?}\n", proc_macro_cwd);
let deps = dependencies
.iter()
.map(|dep| format!("{}={}", dep.name, dep.crate_id.into_raw()))
.map(|dep| format!("{}={:?}", dep.name, dep.crate_id))
.format(", ");
format_to!(buf, " Dependencies: {}\n", deps);
}

View file

@ -1,17 +1,15 @@
//! Discovers tests
use hir::{Crate, Module, ModuleDef, Semantics};
use ide_db::{
base_db::{CrateGraph, CrateId, RootQueryDb},
FileId, RootDatabase,
};
use ide_db::base_db;
use ide_db::{base_db::RootQueryDb, FileId, RootDatabase};
use syntax::TextRange;
use crate::{runnables::runnable_fn, NavigationTarget, Runnable, TryToNav};
#[derive(Debug)]
pub enum TestItemKind {
Crate(CrateId),
Crate(base_db::Crate),
Module,
Function,
}
@ -28,12 +26,12 @@ pub struct TestItem {
}
pub(crate) fn discover_test_roots(db: &RootDatabase) -> Vec<TestItem> {
let crate_graph = db.crate_graph();
crate_graph
db.all_crates()
.iter()
.filter(|&id| crate_graph[id].origin.is_local())
.copied()
.filter(|&id| id.data(db).origin.is_local())
.filter_map(|id| {
let test_id = crate_graph[id].display_name.as_ref()?.to_string();
let test_id = id.extra_data(db).display_name.as_ref()?.to_string();
Some(TestItem {
kind: TestItemKind::Crate(id),
label: test_id.clone(),
@ -47,12 +45,12 @@ pub(crate) fn discover_test_roots(db: &RootDatabase) -> Vec<TestItem> {
.collect()
}
fn find_crate_by_id(crate_graph: &CrateGraph, crate_id: &str) -> Option<CrateId> {
fn find_crate_by_id(db: &RootDatabase, crate_id: &str) -> Option<base_db::Crate> {
// here, we use display_name as the crate id. This is not super ideal, but it works since we
// only show tests for the local crates.
crate_graph.iter().find(|&id| {
crate_graph[id].origin.is_local()
&& crate_graph[id].display_name.as_ref().is_some_and(|x| x.to_string() == crate_id)
db.all_crates().iter().copied().find(|&id| {
id.data(db).origin.is_local()
&& id.extra_data(db).display_name.as_ref().is_some_and(|x| x.to_string() == crate_id)
})
}
@ -115,8 +113,7 @@ pub(crate) fn discover_tests_in_crate_by_test_id(
db: &RootDatabase,
crate_test_id: &str,
) -> Vec<TestItem> {
let crate_graph = db.crate_graph();
let Some(crate_id) = find_crate_by_id(&crate_graph, crate_test_id) else {
let Some(crate_id) = find_crate_by_id(db, crate_test_id) else {
return vec![];
};
discover_tests_in_crate(db, crate_id)
@ -171,12 +168,14 @@ fn find_module_id_and_test_parents(
Some((r, id))
}
pub(crate) fn discover_tests_in_crate(db: &RootDatabase, crate_id: CrateId) -> Vec<TestItem> {
let crate_graph = db.crate_graph();
if !crate_graph[crate_id].origin.is_local() {
pub(crate) fn discover_tests_in_crate(
db: &RootDatabase,
crate_id: base_db::Crate,
) -> Vec<TestItem> {
if !crate_id.data(db).origin.is_local() {
return vec![];
}
let Some(crate_test_id) = &crate_graph[crate_id].display_name else {
let Some(crate_test_id) = &crate_id.extra_data(db).display_name else {
return vec![];
};
let kind = TestItemKind::Crate(crate_id);

View file

@ -1,9 +1,10 @@
use dot::{Id, LabelText};
use ide_db::{
base_db::{CrateGraph, CrateId, Dependency, RootQueryDb, SourceDatabase, Upcast},
FxHashSet, RootDatabase,
base_db::{
BuiltCrateData, BuiltDependency, Crate, ExtraCrateData, RootQueryDb, SourceDatabase,
},
FxHashMap, RootDatabase,
};
use triomphe::Arc;
// Feature: View Crate Graph
//
@ -16,77 +17,80 @@ use triomphe::Arc;
// |---------|-------------|
// | VS Code | **rust-analyzer: View Crate Graph** |
pub(crate) fn view_crate_graph(db: &RootDatabase, full: bool) -> Result<String, String> {
let crate_graph = Upcast::<dyn RootQueryDb>::upcast(db).crate_graph();
let crates_to_render = crate_graph
let all_crates = db.all_crates();
let crates_to_render = all_crates
.iter()
.filter(|krate| {
.copied()
.map(|krate| (krate, (krate.data(db), krate.extra_data(db))))
.filter(|(_, (crate_data, _))| {
if full {
true
} else {
// Only render workspace crates
let root_id =
db.file_source_root(crate_graph[*krate].root_file_id).source_root_id(db);
let root_id = db.file_source_root(crate_data.root_file_id).source_root_id(db);
!db.source_root(root_id).source_root(db).is_library
}
})
.collect();
let graph = DotCrateGraph { graph: crate_graph, crates_to_render };
let graph = DotCrateGraph { crates_to_render };
let mut dot = Vec::new();
dot::render(&graph, &mut dot).unwrap();
Ok(String::from_utf8(dot).unwrap())
}
struct DotCrateGraph {
graph: Arc<CrateGraph>,
crates_to_render: FxHashSet<CrateId>,
struct DotCrateGraph<'db> {
crates_to_render: FxHashMap<Crate, (&'db BuiltCrateData, &'db ExtraCrateData)>,
}
type Edge<'a> = (CrateId, &'a Dependency);
type Edge<'a> = (Crate, &'a BuiltDependency);
impl<'a> dot::GraphWalk<'a, CrateId, Edge<'a>> for DotCrateGraph {
fn nodes(&'a self) -> dot::Nodes<'a, CrateId> {
self.crates_to_render.iter().copied().collect()
impl<'a> dot::GraphWalk<'a, Crate, Edge<'a>> for DotCrateGraph<'_> {
fn nodes(&'a self) -> dot::Nodes<'a, Crate> {
self.crates_to_render.keys().copied().collect()
}
fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> {
self.crates_to_render
.iter()
.flat_map(|krate| {
self.graph[*krate]
.flat_map(|(krate, (crate_data, _))| {
crate_data
.dependencies
.iter()
.filter(|dep| self.crates_to_render.contains(&dep.crate_id))
.filter(|dep| self.crates_to_render.contains_key(&dep.crate_id))
.map(move |dep| (*krate, dep))
})
.collect()
}
fn source(&'a self, edge: &Edge<'a>) -> CrateId {
fn source(&'a self, edge: &Edge<'a>) -> Crate {
edge.0
}
fn target(&'a self, edge: &Edge<'a>) -> CrateId {
fn target(&'a self, edge: &Edge<'a>) -> Crate {
edge.1.crate_id
}
}
impl<'a> dot::Labeller<'a, CrateId, Edge<'a>> for DotCrateGraph {
impl<'a> dot::Labeller<'a, Crate, Edge<'a>> for DotCrateGraph<'_> {
fn graph_id(&'a self) -> Id<'a> {
Id::new("rust_analyzer_crate_graph").unwrap()
}
fn node_id(&'a self, n: &CrateId) -> Id<'a> {
Id::new(format!("_{}", u32::from(n.into_raw()))).unwrap()
fn node_id(&'a self, n: &Crate) -> Id<'a> {
Id::new(format!("_{:?}", n)).unwrap()
}
fn node_shape(&'a self, _node: &CrateId) -> Option<LabelText<'a>> {
fn node_shape(&'a self, _node: &Crate) -> Option<LabelText<'a>> {
Some(LabelText::LabelStr("box".into()))
}
fn node_label(&'a self, n: &CrateId) -> LabelText<'a> {
let name =
self.graph[*n].display_name.as_ref().map_or("(unnamed crate)", |name| name.as_str());
fn node_label(&'a self, n: &Crate) -> LabelText<'a> {
let name = self.crates_to_render[n]
.1
.display_name
.as_ref()
.map_or("(unnamed crate)", |name| name.as_str());
LabelText::LabelStr(name.into())
}
}