diff --git a/crates/project_model/src/project_json.rs b/crates/project_model/src/project_json.rs index e8f1aca61b34..5db1aa1b4f32 100644 --- a/crates/project_model/src/project_json.rs +++ b/crates/project_model/src/project_json.rs @@ -37,6 +37,7 @@ pub struct Crate { pub(crate) is_workspace_member: bool, pub(crate) include: Vec, pub(crate) exclude: Vec, + pub(crate) is_proc_macro: bool, } impl ProjectJson { @@ -96,6 +97,7 @@ impl ProjectJson { is_workspace_member, include, exclude, + is_proc_macro: crate_data.is_proc_macro, } }) .collect::>(), @@ -135,6 +137,8 @@ struct CrateData { proc_macro_dylib_path: Option, is_workspace_member: Option, source: Option, + #[serde(default)] + is_proc_macro: bool, } #[derive(Deserialize, Debug, Clone)] diff --git a/crates/project_model/src/tests.rs b/crates/project_model/src/tests.rs index 302a000d70e1..b50db6f3b859 100644 --- a/crates/project_model/src/tests.rs +++ b/crates/project_model/src/tests.rs @@ -1,12 +1,20 @@ -use std::path::PathBuf; +use std::{ + ops::Deref, + path::{Path, PathBuf}, +}; -use base_db::FileId; +use base_db::{CrateGraph, FileId}; use expect_test::{expect, Expect}; +use paths::AbsPath; +use serde::de::DeserializeOwned; -use crate::{CargoWorkspace, CfgOverrides, ProjectWorkspace, Sysroot, WorkspaceBuildScripts}; +use crate::{ + CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace, Sysroot, + WorkspaceBuildScripts, +}; -fn check(file: &str, expect: Expect) { - let meta = get_test_metadata(file); +fn load_cargo(file: &str) -> CrateGraph { + let meta = get_test_json_file(file); let cargo_workspace = CargoWorkspace::new(meta); let project_workspace = ProjectWorkspace::Cargo { cargo: cargo_workspace, @@ -16,23 +24,21 @@ fn check(file: &str, expect: Expect) { rustc_cfg: Vec::new(), cfg_overrides: CfgOverrides::default(), }; - - let crate_graph = project_workspace.to_crate_graph(None, { - let mut counter = 0; - &mut move |_path| { - counter += 1; - Some(FileId(counter)) - } - }); - - let mut crate_graph = format!("{:#?}", crate_graph); - replace_root(&mut crate_graph, false); - - expect.assert_eq(&crate_graph); + to_crate_graph(project_workspace) } -fn get_test_metadata(file: &str) -> cargo_metadata::Metadata { - let mut json = get_test_data(file).parse::().unwrap(); +fn load_rust_project(file: &str) -> CrateGraph { + let data = get_test_json_file(file); + let project = rooted_project_json(data); + let sysroot = Some(get_fake_sysroot()); + let project_workspace = ProjectWorkspace::Json { project, sysroot, rustc_cfg: Vec::new() }; + to_crate_graph(project_workspace) +} + +fn get_test_json_file(file: &str) -> T { + let file = get_test_path(file); + let data = std::fs::read_to_string(file).unwrap(); + let mut json = data.parse::().unwrap(); fixup_paths(&mut json); return serde_json::from_value(json).unwrap(); @@ -58,16 +64,46 @@ fn replace_root(s: &mut String, direction: bool) { } } -fn get_test_data(file: &str) -> String { +fn get_test_path(file: &str) -> PathBuf { let base = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let file = base.join("test_data").join(file); - std::fs::read_to_string(file).unwrap() + base.join("test_data").join(file) +} + +fn get_fake_sysroot() -> Sysroot { + let sysroot_path = get_test_path("fake-sysroot"); + let sysroot_src_dir = AbsPath::assert(&sysroot_path); + Sysroot::load(&sysroot_src_dir).unwrap() +} + +fn rooted_project_json(data: ProjectJsonData) -> ProjectJson { + let mut root = "$ROOT$".to_string(); + replace_root(&mut root, true); + let path = Path::new(&root); + let base = AbsPath::assert(path); + ProjectJson::new(base, data) +} + +fn to_crate_graph(project_workspace: ProjectWorkspace) -> CrateGraph { + project_workspace.to_crate_graph(None, { + let mut counter = 0; + &mut move |_path| { + counter += 1; + Some(FileId(counter)) + } + }) +} + +fn check_crate_graph(crate_graph: CrateGraph, expect: Expect) { + let mut crate_graph = format!("{:#?}", crate_graph); + replace_root(&mut crate_graph, false); + expect.assert_eq(&crate_graph); } #[test] -fn hello_world_project_model() { - check( - "hello-world-metadata.json", +fn cargo_hello_world_project_model() { + let crate_graph = load_cargo("hello-world-metadata.json"); + check_crate_graph( + crate_graph, expect![[r#" CrateGraph { arena: { @@ -514,3 +550,468 @@ fn hello_world_project_model() { }"#]], ) } + +#[test] +fn rust_project_hello_world_project_model() { + let crate_graph = load_rust_project("hello-world-project.json"); + check_crate_graph( + crate_graph, + expect![[r#" + CrateGraph { + arena: { + CrateId( + 0, + ): CrateData { + root_file_id: FileId( + 1, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "alloc", + ), + canonical_name: "alloc", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [ + Dependency { + crate_id: CrateId( + 1, + ), + name: CrateName( + "core", + ), + }, + ], + proc_macro: [], + }, + CrateId( + 10, + ): CrateData { + root_file_id: FileId( + 11, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "unwind", + ), + canonical_name: "unwind", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [], + proc_macro: [], + }, + CrateId( + 7, + ): CrateData { + root_file_id: FileId( + 8, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "std_detect", + ), + canonical_name: "std_detect", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [], + proc_macro: [], + }, + CrateId( + 4, + ): CrateData { + root_file_id: FileId( + 5, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "proc_macro", + ), + canonical_name: "proc_macro", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [ + Dependency { + crate_id: CrateId( + 6, + ), + name: CrateName( + "std", + ), + }, + ], + proc_macro: [], + }, + CrateId( + 1, + ): CrateData { + root_file_id: FileId( + 2, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "core", + ), + canonical_name: "core", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [], + proc_macro: [], + }, + CrateId( + 11, + ): CrateData { + root_file_id: FileId( + 12, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "hello_world", + ), + canonical_name: "hello_world", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [ + Dependency { + crate_id: CrateId( + 1, + ), + name: CrateName( + "core", + ), + }, + Dependency { + crate_id: CrateId( + 0, + ), + name: CrateName( + "alloc", + ), + }, + Dependency { + crate_id: CrateId( + 6, + ), + name: CrateName( + "std", + ), + }, + ], + proc_macro: [], + }, + CrateId( + 8, + ): CrateData { + root_file_id: FileId( + 9, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "term", + ), + canonical_name: "term", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [], + proc_macro: [], + }, + CrateId( + 5, + ): CrateData { + root_file_id: FileId( + 6, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "profiler_builtins", + ), + canonical_name: "profiler_builtins", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [], + proc_macro: [], + }, + CrateId( + 2, + ): CrateData { + root_file_id: FileId( + 3, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "panic_abort", + ), + canonical_name: "panic_abort", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [], + proc_macro: [], + }, + CrateId( + 9, + ): CrateData { + root_file_id: FileId( + 10, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "test", + ), + canonical_name: "test", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [], + proc_macro: [], + }, + CrateId( + 6, + ): CrateData { + root_file_id: FileId( + 7, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "std", + ), + canonical_name: "std", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [ + Dependency { + crate_id: CrateId( + 0, + ), + name: CrateName( + "alloc", + ), + }, + Dependency { + crate_id: CrateId( + 1, + ), + name: CrateName( + "core", + ), + }, + Dependency { + crate_id: CrateId( + 2, + ), + name: CrateName( + "panic_abort", + ), + }, + Dependency { + crate_id: CrateId( + 3, + ), + name: CrateName( + "panic_unwind", + ), + }, + Dependency { + crate_id: CrateId( + 5, + ), + name: CrateName( + "profiler_builtins", + ), + }, + Dependency { + crate_id: CrateId( + 7, + ), + name: CrateName( + "std_detect", + ), + }, + Dependency { + crate_id: CrateId( + 8, + ), + name: CrateName( + "term", + ), + }, + Dependency { + crate_id: CrateId( + 9, + ), + name: CrateName( + "test", + ), + }, + Dependency { + crate_id: CrateId( + 10, + ), + name: CrateName( + "unwind", + ), + }, + ], + proc_macro: [], + }, + CrateId( + 3, + ): CrateData { + root_file_id: FileId( + 4, + ), + edition: Edition2018, + display_name: Some( + CrateDisplayName { + crate_name: CrateName( + "panic_unwind", + ), + canonical_name: "panic_unwind", + }, + ), + cfg_options: CfgOptions( + [], + ), + potential_cfg_options: CfgOptions( + [], + ), + env: Env { + entries: {}, + }, + dependencies: [], + proc_macro: [], + }, + }, + }"#]], + ); +} + +#[test] +fn rust_project_is_proc_macro_has_proc_macro_dep() { + let crate_graph = load_rust_project("is-proc-macro-project.json"); + // Since the project only defines one crate (outside the sysroot crates), + // it should be the one with the biggest Id. + let crate_id = crate_graph.iter().max().unwrap(); + let crate_data = &crate_graph[crate_id]; + // Assert that the project crate with `is_proc_macro` has a dependency + // on the proc_macro sysroot crate. + crate_data.dependencies.iter().find(|&dep| dep.name.deref() == "proc_macro").unwrap(); +} diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs index 41bd668e4768..bfc0f144aac2 100644 --- a/crates/project_model/src/workspace.rs +++ b/crates/project_model/src/workspace.rs @@ -446,10 +446,20 @@ fn project_json_to_crate_graph( for (from, krate) in project.crates() { if let Some(&from) = crates.get(&from) { - if let Some((public_deps, _proc_macro)) = &sysroot_deps { + if let Some((public_deps, libproc_macro)) = &sysroot_deps { for (name, to) in public_deps.iter() { add_dep(&mut crate_graph, from, name.clone(), *to) } + if krate.is_proc_macro { + if let Some(proc_macro) = libproc_macro { + add_dep( + &mut crate_graph, + from, + CrateName::new("proc_macro").unwrap(), + *proc_macro, + ); + } + } } for dep in &krate.deps { diff --git a/crates/project_model/test_data/fake-sysroot/alloc/src/lib.rs b/crates/project_model/test_data/fake-sysroot/alloc/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/fake-sysroot/core/src/lib.rs b/crates/project_model/test_data/fake-sysroot/core/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/fake-sysroot/panic_abort/src/lib.rs b/crates/project_model/test_data/fake-sysroot/panic_abort/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/fake-sysroot/panic_unwind/src/lib.rs b/crates/project_model/test_data/fake-sysroot/panic_unwind/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/fake-sysroot/proc_macro/src/lib.rs b/crates/project_model/test_data/fake-sysroot/proc_macro/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/fake-sysroot/profiler_builtins/src/lib.rs b/crates/project_model/test_data/fake-sysroot/profiler_builtins/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/fake-sysroot/std/src/lib.rs b/crates/project_model/test_data/fake-sysroot/std/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/fake-sysroot/stdarch/crates/std_detect/src/lib.rs b/crates/project_model/test_data/fake-sysroot/stdarch/crates/std_detect/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/fake-sysroot/term/src/lib.rs b/crates/project_model/test_data/fake-sysroot/term/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/fake-sysroot/test/src/lib.rs b/crates/project_model/test_data/fake-sysroot/test/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/fake-sysroot/unwind/src/lib.rs b/crates/project_model/test_data/fake-sysroot/unwind/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/project_model/test_data/hello-world-project.json b/crates/project_model/test_data/hello-world-project.json new file mode 100644 index 000000000000..b27ab1f42b57 --- /dev/null +++ b/crates/project_model/test_data/hello-world-project.json @@ -0,0 +1,12 @@ +{ + "sysroot_src": null, + "crates": [ + { + "display_name": "hello_world", + "root_module": "$ROOT$src/lib.rs", + "edition": "2018", + "deps": [], + "is_workspace_member": true + } + ] +} diff --git a/crates/project_model/test_data/is-proc-macro-project.json b/crates/project_model/test_data/is-proc-macro-project.json new file mode 100644 index 000000000000..5d500a4729f5 --- /dev/null +++ b/crates/project_model/test_data/is-proc-macro-project.json @@ -0,0 +1,13 @@ +{ + "sysroot_src": null, + "crates": [ + { + "display_name": "is_proc_macro", + "root_module": "$ROOT$src/lib.rs", + "edition": "2018", + "deps": [], + "is_workspace_member": true, + "is_proc_macro": true + } + ] +} diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index 000a95b10038..6f00da3178a0 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc @@ -578,6 +578,8 @@ interface Crate { /// the `env!` macro env: : { [key: string]: string; }, + /// Whether the crate is a proc-macro crate. + is_proc_macro: bool; /// For proc-macro crates, path to compiled /// proc-macro (.so file). proc_macro_dylib_path?: string; @@ -597,7 +599,7 @@ Specifically, the `roots` setup will be different eventually. There are three ways to feed `rust-project.json` to rust-analyzer: -* Place `rust-project.json` file at the root of the project, and rust-anlayzer will discover it. +* Place `rust-project.json` file at the root of the project, and rust-analyzer will discover it. * Specify `"rust-analyzer.linkedProjects": [ "path/to/rust-project.json" ]` in the settings (and make sure that your LSP client sends settings as a part of initialize request). * Specify `"rust-analyzer.linkedProjects": [ { "roots": [...], "crates": [...] }]` inline.