diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index fb69e667ad..61a17da9aa 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -329,12 +329,10 @@ pub struct CrateData { } impl CrateData { - /** - Check if [`other`] is almost equal to [`self`]. - This method has some obscure bits. These are mostly there to be compliant with - some patches. References to the patches are given. - */ - pub fn almost_eq(&self, other: &CrateData) -> bool { + /// Check if [`other`] is almost equal to [`self`] ignoring `CrateOrigin` value. + pub fn eq_ignoring_origin(&self, other: &CrateData) -> bool { + // This method has some obscure bits. These are mostly there to be compliant with + // some patches. References to the patches are given. if self.root_file_id != other.root_file_id { return false; } @@ -356,16 +354,16 @@ impl CrateData { } let mut opts = self.cfg_options.clone(); - opts.apply_diff(CfgDiff { - disable: other.cfg_options.clone().into_iter().collect(), - enable: vec![], - }); + opts.apply_diff( + CfgDiff::new(vec![], other.cfg_options.clone().into_iter().collect()) + .expect("CfgOptions were expected to contain no duplicates."), + ); let mut cfgs = opts.into_iter(); if let Some(cfg) = cfgs.next() { // Don't care if rust_analyzer CfgAtom is the only cfg in the difference set of self's and other's cfgs. // https://github.com/rust-lang/rust-analyzer/blob/0840038f02daec6ba3238f05d8caa037d28701a0/crates/project-model/src/workspace.rs#L894 - if !cfgs.next().is_none() || cfg.to_string() != "rust_analyzer" { + if cfgs.next().is_some() || cfg.to_string() != "rust_analyzer" { return false; } } @@ -686,41 +684,35 @@ impl CrateGraph { /// Note that for deduplication to fully work, `self`'s crate dependencies must be sorted by crate id. /// If the crate dependencies were sorted, the resulting graph from this `extend` call will also have the crate dependencies sorted. pub fn extend(&mut self, mut other: CrateGraph, proc_macros: &mut ProcMacroPaths) { - enum ExtendStrategy { - Dedup(CrateId), - Replace(CrateId), - } - let topo = other.crates_in_topological_order(); let mut id_map: FxHashMap = FxHashMap::default(); for topo in topo { let crate_data = &mut other.arena[topo]; - crate_data.dependencies.iter_mut().for_each(|dep| { - dep.crate_id = id_map[&dep.crate_id]; - }); + crate_data.dependencies.iter_mut().for_each(|dep| dep.crate_id = id_map[&dep.crate_id]); crate_data.dependencies.sort_by_key(|dep| dep.crate_id); let res = self.arena.iter().find_map(|(id, data)| { - if data.almost_eq(crate_data) { + if data.eq_ignoring_origin(crate_data) { if data.origin.is_lib() && crate_data.origin.is_local() { // See #15656 for a relevant example. - return Some(ExtendStrategy::Replace(id)); + return Some((id, true)); } - return Some(ExtendStrategy::Dedup(id)); + return Some((id, false)); } None }); - if let Some(res) = res { - match res { - ExtendStrategy::Dedup(res) => id_map.insert(topo, res), - ExtendStrategy::Replace(res) => { - let id = self.arena.alloc(crate_data.clone()); - let _ = self.remove_and_replace(res, id); - id_map.insert(topo, id) + if let Some((res, should_update_lib_to_local)) = res { + id_map.insert(topo, res); + if should_update_lib_to_local { + let origin_old = self.arena[res].origin.clone(); + assert!(origin_old.is_lib()); + + if let CrateOrigin::Library { repo, name } = origin_old { + self.arena[res].origin = CrateOrigin::Local { repo, name: Some(name) }; } - }; + } } else { let id = self.arena.alloc(crate_data.clone()); id_map.insert(topo, id); diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs index 90dba008ad..0aeb0b0505 100644 --- a/crates/cfg/src/lib.rs +++ b/crates/cfg/src/lib.rs @@ -115,8 +115,8 @@ impl<'a> IntoIterator for &'a CfgOptions { #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct CfgDiff { // Invariants: No duplicates, no atom that's both in `enable` and `disable`. - pub enable: Vec, - pub disable: Vec, + enable: Vec, + disable: Vec, } impl CfgDiff { diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs index 7815b9dda7..65c6f0b256 100644 --- a/crates/project-model/src/tests.rs +++ b/crates/project-model/src/tests.rs @@ -249,3 +249,53 @@ fn crate_graph_dedup() { crate_graph.extend(regex_crate_graph, &mut regex_proc_macros); assert_eq!(crate_graph.iter().count(), 118); } + +#[test] +fn test_deduplicate_crate_differing_in_origin() { + let path_map = &mut Default::default(); + let (mut crate_graph, _proc_macros) = + load_cargo_with_sysroot(path_map, "deduplication_crate_graph_A.json"); + crate_graph.sort_deps(); + let (crate_graph_1, mut _proc_macros_2) = + load_cargo_with_sysroot(path_map, "deduplication_crate_graph_B.json"); + + crate_graph.extend(crate_graph_1, &mut _proc_macros_2); + + let mut crates_named_p1 = vec![]; + for id in crate_graph.iter() { + let krate = &crate_graph[id]; + if let Some(name) = krate.display_name.as_ref() { + if name.to_string() == "p1" { + crates_named_p1.push(krate); + } + } + } + + assert!(crates_named_p1.len() == 1); + assert!(crates_named_p1[0].origin.is_local()); +} + +#[test] +fn test_deduplicate_crate_differing_in_origin_in_rev_resolution_order() { + let path_map = &mut Default::default(); + let (mut crate_graph, _proc_macros) = + load_cargo_with_sysroot(path_map, "deduplication_crate_graph_B.json"); + crate_graph.sort_deps(); + let (crate_graph_1, mut _proc_macros_2) = + load_cargo_with_sysroot(path_map, "deduplication_crate_graph_A.json"); + + crate_graph.extend(crate_graph_1, &mut _proc_macros_2); + + let mut crates_named_p1 = vec![]; + for id in crate_graph.iter() { + let krate = &crate_graph[id]; + if let Some(name) = krate.display_name.as_ref() { + if name.to_string() == "p1" { + crates_named_p1.push(krate); + } + } + } + + assert!(crates_named_p1.len() == 1); + assert!(crates_named_p1[0].origin.is_local()); +} diff --git a/crates/project-model/test_data/deduplication_crate_graph_A.json b/crates/project-model/test_data/deduplication_crate_graph_A.json new file mode 100644 index 0000000000..3f627082f9 --- /dev/null +++ b/crates/project-model/test_data/deduplication_crate_graph_A.json @@ -0,0 +1,66 @@ +{ + "packages": [ + { + "name": "p1", + "version": "0.1.0", + "id": "p1 0.1.0 (path+file:///path/to/project/projects/p1)", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "p1", + "src_path": "/path/to/project/projects/p1/src/lib.rs", + "edition": "2021", + "doc": true, + "doctest": true, + "test": true + } + ], + "features": {}, + "manifest_path": "/path/to/project/projects/p1/Cargo.toml", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2021", + "links": null, + "default_run": null, + "rust_version": null + } + ], + "workspace_members": [ + "p1 0.1.0 (path+file:///path/to/project/projects/p1)" + ], + "workspace_default_members": [ + "p1 0.1.0 (path+file:///path/to/project/projects/p1)" + ], + "resolve": { + "nodes": [ + { + "id": "p1 0.1.0 (path+file:///path/to/project/projects/p1)", + "dependencies": [], + "deps": [], + "features": [] + } + ], + "root": "p1 0.1.0 (path+file:///path/to/project/projects/p1)" + }, + "target_directory": "/path/to/project/projects/p1/target", + "version": 1, + "workspace_root": "/path/to/project/projects/p1", + "metadata": null +} \ No newline at end of file diff --git a/crates/project-model/test_data/deduplication_crate_graph_B.json b/crates/project-model/test_data/deduplication_crate_graph_B.json new file mode 100644 index 0000000000..a2bf1af044 --- /dev/null +++ b/crates/project-model/test_data/deduplication_crate_graph_B.json @@ -0,0 +1,140 @@ +{ + "packages": [ + { + "name": "p1", + "version": "0.1.0", + "id": "p1 0.1.0 (path+file:///path/to/project/projects/p1)", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "p1", + "src_path": "/path/to/project/projects/p1/src/lib.rs", + "edition": "2021", + "doc": true, + "doctest": true, + "test": true + } + ], + "features": {}, + "manifest_path": "/path/to/project/projects/p1/Cargo.toml", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2021", + "links": null, + "default_run": null, + "rust_version": null + }, + { + "name": "p2", + "version": "0.1.0", + "id": "p2 0.1.0 (path+file:///path/to/project/projects/p2)", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [ + { + "name": "p1", + "source": null, + "req": "*", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null, + "registry": null, + "path": "/path/to/project/projects/p1" + } + ], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "p2", + "src_path": "/path/to/project/projects/p2/src/lib.rs", + "edition": "2021", + "doc": true, + "doctest": true, + "test": true + } + ], + "features": {}, + "manifest_path": "/path/to/project/projects/p2/Cargo.toml", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2021", + "links": null, + "default_run": null, + "rust_version": null + } + ], + "workspace_members": [ + "p2 0.1.0 (path+file:///path/to/project/projects/p2)" + ], + "workspace_default_members": [ + "p2 0.1.0 (path+file:///path/to/project/projects/p2)" + ], + "resolve": { + "nodes": [ + { + "id": "p1 0.1.0 (path+file:///path/to/project/projects/p1)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "p2 0.1.0 (path+file:///path/to/project/projects/p2)", + "dependencies": [ + "p1 0.1.0 (path+file:///path/to/project/projects/p1)" + ], + "deps": [ + { + "name": "p1", + "pkg": "p1 0.1.0 (path+file:///path/to/project/projects/p1)", + "dep_kinds": [ + { + "kind": null, + "target": null + } + ] + } + ], + "features": [] + } + ], + "root": "p2 0.1.0 (path+file:///path/to/project/projects/p2)" + }, + "target_directory": "/path/to/project/projects/p2/target", + "version": 1, + "workspace_root": "/path/to/project/projects/p2", + "metadata": null +} \ No newline at end of file diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs index 423c07183e..d599142989 100644 --- a/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/crates/rust-analyzer/tests/slow-tests/main.rs @@ -21,12 +21,12 @@ use std::{collections::HashMap, path::PathBuf, time::Instant}; use lsp_types::{ notification::DidOpenTextDocument, request::{ - CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest, Rename, + CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest, WillRenameFiles, WorkspaceSymbolRequest, }, CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams, DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams, - PartialResultParams, Position, Range, RenameFilesParams, RenameParams, TextDocumentItem, + PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams, }; use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams}; @@ -1131,207 +1131,3 @@ version = "0.0.0" server.request::(Default::default(), json!([])); } - -#[test] -fn test_deduplicate_crate_differing_in_origin() { - let fixture = r#" -//- /projects/p1/Cargo.toml -[package] -name = "p1" -version = "0.0.0" - -//- /projects/p1/src/lib.rs -pub fn add2(left: usize, right: usize) -> usize { - left + right -} - -//- /projects/p2/Cargo.toml -[package] -name = "p2" -version = "0.0.0" - -[dependencies] -p1 = { path = "../p1" } - -//- /projects/p2/src/lib.rs -use p1::add2; - -pub fn bar() {} - "#; - - let server = Project::with_fixture(fixture) - .with_config(serde_json::json!({ - "linkedProjects" : [ - "./projects/p1/Cargo.toml", - "./projects/p2/Cargo.toml" - ], - } - )) - .with_config(serde_json::json!({ - "cargo": { "sysroot": null }, - })) - .server() - .wait_until_workspace_is_loaded(); - - let doc_id = server.doc_id("./projects/p2/src/lib.rs"); - let doc2_id = server.doc_id("./projects/p1/src/lib.rs"); - - server.request::( - RenameParams { - text_document_position: TextDocumentPositionParams { - text_document: doc_id.clone(), - position: Position { line: 0, character: 8 }, - }, - new_name: "ABC".to_owned(), - work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, - }, - json!({ - "documentChanges": [ - { - "textDocument": { - "uri": doc2_id.uri, - "version": null - }, - "edits": [ - { - "range": { - "start": { - "line": 0, - "character": 7 - }, - "end": { - "line": 0, - "character": 11 - } - }, - "newText": "ABC" - } - ] - }, - { - "textDocument": { - "uri": doc_id.uri, - "version": null - }, - "edits": [ - { - "range": { - "start": { - "line": 0, - "character": 8 - }, - "end": { - "line": 0, - "character": 12 - } - }, - "newText": "ABC" - } - ] - }, - ] - }), - ); -} - -#[test] -fn test_deduplicate_crate_differing_in_origin_in_rev_resolution_order() { - let fixture = r#" -//- /projects/p1/Cargo.toml -[package] -name = "p1" -version = "0.0.0" - -//- /projects/p1/src/lib.rs -pub fn add2(left: usize, right: usize) -> usize { - left + right -} - -//- /projects/p2/Cargo.toml -[package] -name = "p2" -version = "0.0.0" - -[dependencies] -p1 = { path = "../p1" } - -//- /projects/p2/src/lib.rs -use p1::add2; - -pub fn bar() {} - "#; - - let server = Project::with_fixture(fixture) - .with_config(serde_json::json!({ - "linkedProjects" : [ - "./projects/p2/Cargo.toml", - "./projects/p1/Cargo.toml", - ], - } - )) - .with_config(serde_json::json!({ - "cargo": { "sysroot": null }, - })) - .server() - .wait_until_workspace_is_loaded(); - - let doc_id = server.doc_id("./projects/p2/src/lib.rs"); - let doc2_id = server.doc_id("./projects/p1/src/lib.rs"); - - server.request::( - RenameParams { - text_document_position: TextDocumentPositionParams { - text_document: doc_id.clone(), - position: Position { line: 0, character: 8 }, - }, - new_name: "ABC".to_owned(), - work_done_progress_params: WorkDoneProgressParams { work_done_token: None }, - }, - json!({ - "documentChanges": [ - { - "textDocument": { - "uri": doc2_id.uri, - "version": null - }, - "edits": [ - { - "range": { - "start": { - "line": 0, - "character": 7 - }, - "end": { - "line": 0, - "character": 11 - } - }, - "newText": "ABC" - } - ] - }, - { - "textDocument": { - "uri": doc_id.uri, - "version": null - }, - "edits": [ - { - "range": { - "start": { - "line": 0, - "character": 8 - }, - "end": { - "line": 0, - "character": 12 - } - }, - "newText": "ABC" - } - ] - }, - ] - }), - ); -}