Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Dedup duplicate crates with differing origins in CrateGraph construction #15754

Merged
merged 7 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions crates/base-db/src/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use vfs::{file_set::FileSet, VfsPath};

use crate::{
input::{CrateName, CrateOrigin, LangCrateOrigin},
Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition,
FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacros, ReleaseChannel,
SourceDatabaseExt, SourceRoot, SourceRootId,
Change, CrateDisplayName, CrateGraph, CrateId, Dependency, DependencyKind, Edition, Env,
FileId, FilePosition, FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError,
ProcMacros, ReleaseChannel, SourceDatabaseExt, SourceRoot, SourceRootId,
};

pub const WORKSPACE: SourceRootId = SourceRootId(0);
Expand Down Expand Up @@ -237,7 +237,12 @@ impl ChangeFixture {
crate_graph
.add_dep(
from_id,
Dependency::with_prelude(CrateName::new(&to).unwrap(), to_id, prelude),
Dependency::with_prelude(
CrateName::new(&to).unwrap(),
to_id,
prelude,
DependencyKind::Normal,
),
)
.unwrap();
}
Expand Down Expand Up @@ -275,7 +280,14 @@ impl ChangeFixture {

for krate in all_crates {
crate_graph
.add_dep(krate, Dependency::new(CrateName::new("core").unwrap(), core_crate))
.add_dep(
krate,
Dependency::new(
CrateName::new("core").unwrap(),
core_crate,
DependencyKind::Normal,
),
)
.unwrap();
}
}
Expand Down Expand Up @@ -317,7 +329,11 @@ impl ChangeFixture {
crate_graph
.add_dep(
krate,
Dependency::new(CrateName::new("proc_macros").unwrap(), proc_macros_crate),
Dependency::new(
CrateName::new("proc_macros").unwrap(),
proc_macros_crate,
DependencyKind::Normal,
),
)
.unwrap();
}
Expand Down
189 changes: 161 additions & 28 deletions crates/base-db/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ impl CrateOrigin {
pub fn is_local(&self) -> bool {
matches!(self, CrateOrigin::Local { .. })
}

pub fn is_lib(&self) -> bool {
matches!(self, CrateOrigin::Library { .. })
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -324,6 +328,62 @@ pub struct CrateData {
pub channel: Option<ReleaseChannel>,
}

impl CrateData {
/// Check if [`other`] is almost equal to [`self`] ignoring `CrateOrigin` value.
pub fn eq_ignoring_origin_and_deps(&self, other: &CrateData, ignore_dev_deps: bool) -> 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;
}

if self.display_name != other.display_name {
return false;
}

if self.is_proc_macro != other.is_proc_macro {
return false;
}

if self.edition != other.edition {
return false;
}

if self.version != other.version {
return false;
}

let mut opts = self.cfg_options.difference(&other.cfg_options);
if let Some(it) = opts.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 it.to_string() != "rust_analyzer" {
return false;
}

if let Some(_) = opts.next() {
return false;
}
}

if self.env != other.env {
return false;
}

let slf_deps = self.dependencies.iter();
let other_deps = other.dependencies.iter();

if ignore_dev_deps {
return slf_deps
.clone()
.filter(|it| it.kind != DependencyKind::Dev)
.eq(other_deps.clone().filter(|it| it.kind != DependencyKind::Dev));
}

slf_deps.eq(other_deps)
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Edition {
Edition2015,
Expand Down Expand Up @@ -351,26 +411,43 @@ impl Env {
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum DependencyKind {
Normal,
Dev,
Build,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't take this too seriously, as I have retreated to my Zig ivory tower in Lisbon some years ago and am mostly out of touch with the current goings of rust-analyzer, but I would say this is a significant architectural bug.

By design, this layer of rust-analyzer knows nothing about cargo specific concepts. dev-vs-build-vs normal is 100% Cargo concept. rustc knows nothing about these words. As such, any special handling of these concepts should happen in workspace.rs, not here.

The motivation for this design is two-fold:

  • practically, Cargo is a build system, there might be others. In the core analysis parts, we shouldn't be prioritizing Cargo over other (current and future) build systems
  • theoretically, it is important that the internal objects in rust-analyzer's semantic model reflect the physical reality of rustc. In that physical reality, cargo packages do not exists (and -dev as a concept applies to a package), there are only units of compilation.

cc @Veykril

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't take this too seriously,

Not at all, you are completely right, this seems incorrect. This was a mistake on my part (I proposed this change somewhere I believe, might've been in DMs).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to check for equality we needed to filter out dev dependencies and thus we ended up with the current state of things. The alternatives were

  1. To filter them in project_model : I am looking into this since @matklad 's first comment here, I do not think it is going to be easy.
  2. Adding project-model as a dependency of base-db which is ofc super wrong to do. ( As a matter of fact we already do have a DependencyKind under project-model named DepKind )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But let me say that I also agree with matklad's concerns so I will look for a way to rectify this.


#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Dependency {
pub crate_id: CrateId,
pub name: CrateName,
kind: DependencyKind,
prelude: bool,
}

impl Dependency {
pub fn new(name: CrateName, crate_id: CrateId) -> Self {
Self { name, crate_id, prelude: true }
pub fn new(name: CrateName, crate_id: CrateId, kind: DependencyKind) -> Self {
Self { name, crate_id, prelude: true, kind }
}

pub fn with_prelude(name: CrateName, crate_id: CrateId, prelude: bool) -> Self {
Self { name, crate_id, prelude }
pub fn with_prelude(
name: CrateName,
crate_id: CrateId,
prelude: bool,
kind: DependencyKind,
) -> Self {
Self { name, crate_id, prelude, kind }
}

/// Whether this dependency is to be added to the depending crate's extern prelude.
pub fn is_prelude(&self) -> bool {
self.prelude
}

pub fn kind(&self) -> DependencyKind {
self.kind
}
}

impl CrateGraph {
Expand Down Expand Up @@ -574,23 +651,46 @@ impl CrateGraph {
pub fn extend(&mut self, mut other: CrateGraph, proc_macros: &mut ProcMacroPaths) {
let topo = other.crates_in_topological_order();
let mut id_map: FxHashMap<CrateId, CrateId> = 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.sort_by_key(|dep| dep.crate_id);

let res = self.arena.iter().find_map(
|(id, data)| {
if data == crate_data {
Some(id)
} else {
None
let res = self.arena.iter().find_map(|(id, data)| {
match (&data.origin, &crate_data.origin) {
(a, b) if a == b => {
if data.eq_ignoring_origin_and_deps(&crate_data, false) {
return Some((id, false));
}
}
(a @ CrateOrigin::Local { .. }, CrateOrigin::Library { .. })
| (a @ CrateOrigin::Library { .. }, CrateOrigin::Local { .. }) => {
// If the origins differ, check if the two crates are equal without
// considering the dev dependencies, if they are, they most likely are in
// different loaded workspaces which may cause issues. We keep the local
// version and discard the library one as the local version may have
// dev-dependencies that we want to keep resolving. See #15656 for more
// information.
if data.eq_ignoring_origin_and_deps(&crate_data, true) {
return Some((id, if a.is_local() { false } else { true }));
}
}
},
);
if let Some(res) = res {
(_, _) => return None,
}

None
});

if let Some((res, should_update_lib_to_local)) = res {
id_map.insert(topo, res);
if should_update_lib_to_local {
assert!(self.arena[res].origin.is_lib());
assert!(crate_data.origin.is_local());
self.arena[res].origin = crate_data.origin.clone();

// Move local's dev dependencies into the newly-local-formerly-lib crate.
self.arena[res].dependencies = crate_data.dependencies.clone();
}
} else {
let id = self.arena.alloc(crate_data.clone());
id_map.insert(topo, id);
Expand Down Expand Up @@ -636,9 +736,11 @@ impl CrateGraph {
match (cfg_if, std) {
(Some(cfg_if), Some(std)) => {
self.arena[cfg_if].dependencies.clear();
self.arena[std]
.dependencies
.push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if));
self.arena[std].dependencies.push(Dependency::new(
CrateName::new("cfg_if").unwrap(),
cfg_if,
DependencyKind::Normal,
));
true
}
_ => false,
Expand All @@ -658,6 +760,8 @@ impl ops::Index<CrateId> for CrateGraph {
}

impl CrateData {
/// Add a dependency to `self` without checking if the dependency
// is existent among `self.dependencies`.
fn add_dep(&mut self, dep: Dependency) {
self.dependencies.push(dep)
}
Expand Down Expand Up @@ -759,7 +863,7 @@ impl fmt::Display for CyclicDependenciesError {

#[cfg(test)]
mod tests {
use crate::CrateOrigin;
use crate::{CrateOrigin, DependencyKind};

use super::{CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId};

Expand Down Expand Up @@ -806,13 +910,22 @@ mod tests {
None,
);
assert!(graph
.add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
.add_dep(
crate1,
Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
)
.is_ok());
assert!(graph
.add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3))
.add_dep(
crate2,
Dependency::new(CrateName::new("crate3").unwrap(), crate3, DependencyKind::Normal)
)
.is_ok());
assert!(graph
.add_dep(crate3, Dependency::new(CrateName::new("crate1").unwrap(), crate1))
.add_dep(
crate3,
Dependency::new(CrateName::new("crate1").unwrap(), crate1, DependencyKind::Normal)
)
.is_err());
}

Expand Down Expand Up @@ -846,10 +959,16 @@ mod tests {
None,
);
assert!(graph
.add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
.add_dep(
crate1,
Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
)
.is_ok());
assert!(graph
.add_dep(crate2, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
.add_dep(
crate2,
Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
)
.is_err());
}

Expand Down Expand Up @@ -896,10 +1015,16 @@ mod tests {
None,
);
assert!(graph
.add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
.add_dep(
crate1,
Dependency::new(CrateName::new("crate2").unwrap(), crate2, DependencyKind::Normal)
)
.is_ok());
assert!(graph
.add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3))
.add_dep(
crate2,
Dependency::new(CrateName::new("crate3").unwrap(), crate3, DependencyKind::Normal)
)
.is_ok());
}

Expand Down Expand Up @@ -935,12 +1060,20 @@ mod tests {
assert!(graph
.add_dep(
crate1,
Dependency::new(CrateName::normalize_dashes("crate-name-with-dashes"), crate2)
Dependency::new(
CrateName::normalize_dashes("crate-name-with-dashes"),
crate2,
DependencyKind::Normal
)
)
.is_ok());
assert_eq!(
graph[crate1].dependencies,
vec![Dependency::new(CrateName::new("crate_name_with_dashes").unwrap(), crate2)]
vec![Dependency::new(
CrateName::new("crate_name_with_dashes").unwrap(),
crate2,
DependencyKind::Normal
)]
);
}
}
1 change: 1 addition & 0 deletions crates/base-db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use rustc_hash::FxHashSet;
use syntax::{ast, Parse, SourceFile, TextRange, TextSize};
use triomphe::Arc;

pub use crate::input::DependencyKind;
pub use crate::{
change::Change,
input::{
Expand Down
7 changes: 7 additions & 0 deletions crates/cfg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ impl CfgOptions {
self.enabled.insert(CfgAtom::KeyValue { key, value });
}

pub fn difference<'a>(
&'a self,
other: &'a CfgOptions,
) -> impl Iterator<Item = &'a CfgAtom> + 'a {
self.enabled.difference(&other.enabled)
}

pub fn apply_diff(&mut self, diff: CfgDiff) {
for atom in diff.enable {
self.enabled.insert(atom);
Expand Down
3 changes: 2 additions & 1 deletion crates/project-model/src/project_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
//! user explores them belongs to that extension (it's totally valid to change
//! rust-project.json over time via configuration request!)

use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition};
use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, DependencyKind, Edition};
use la_arena::RawIdx;
use paths::{AbsPath, AbsPathBuf};
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -135,6 +135,7 @@ impl ProjectJson {
Dependency::new(
dep_data.name,
CrateId::from_raw(RawIdx::from(dep_data.krate as u32)),
DependencyKind::Normal,
)
})
.collect::<Vec<_>>(),
Expand Down
Loading