Repository: rust-analyzer/rust-analyzer
Pull request #10385 (2021-09-28 19:47:25)

Make `extern crate test;` work
This implements support for dependencies that are not added to the extern prelude of a crate, and add the `test` crate from the sysroot as such a dependency.

This does mean we now index `test` on startup, but I didn't notice much of a difference (and also, r-a can be used while it is still indexing).

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/6714

bors r+


Patch:
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs
index cc1de54dfb..017684fc83 100644
--- a/crates/base_db/src/fixture.rs
+++ b/crates/base_db/src/fixture.rs
@@ -10,8 +10,8 @@ use tt::Subtree;
 use vfs::{file_set::FileSet, VfsPath};
 
 use crate::{
-    input::CrateName, Change, CrateDisplayName, CrateGraph, CrateId, Edition, Env, FileId,
-    FilePosition, FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError,
+    input::CrateName, Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env,
+    FileId, FilePosition, FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError,
     SourceDatabaseExt, SourceRoot, SourceRootId,
 };
 
@@ -144,8 +144,9 @@ impl ChangeFixture {
                 let prev = crates.insert(crate_name.clone(), crate_id);
                 assert!(prev.is_none());
                 for dep in meta.deps {
+                    let prelude = meta.extern_prelude.contains(&dep);
                     let dep = CrateName::normalize_dashes(&dep);
-                    crate_deps.push((crate_name.clone(), dep))
+                    crate_deps.push((crate_name.clone(), dep, prelude))
                 }
             } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
                 assert!(default_crate_root.is_none());
@@ -173,10 +174,15 @@ impl ChangeFixture {
                 Default::default(),
             );
         } else {
-            for (from, to) in crate_deps {
+            for (from, to, prelude) in crate_deps {
                 let from_id = crates[&from];
                 let to_id = crates[&to];
-                crate_graph.add_dep(from_id, CrateName::new(&to).unwrap(), to_id).unwrap();
+                crate_graph
+                    .add_dep(
+                        from_id,
+                        Dependency::with_prelude(CrateName::new(&to).unwrap(), to_id, prelude),
+                    )
+                    .unwrap();
             }
         }
 
@@ -203,7 +209,9 @@ impl ChangeFixture {
             );
 
             for krate in all_crates {
-                crate_graph.add_dep(krate, CrateName::new("core").unwrap(), core_crate).unwrap();
+                crate_graph
+                    .add_dep(krate, Dependency::new(CrateName::new("core").unwrap(), core_crate))
+                    .unwrap();
             }
         }
 
@@ -235,7 +243,10 @@ impl ChangeFixture {
 
             for krate in all_crates {
                 crate_graph
-                    .add_dep(krate, CrateName::new("proc_macros").unwrap(), proc_macros_crate)
+                    .add_dep(
+                        krate,
+                        Dependency::new(CrateName::new("proc_macros").unwrap(), proc_macros_crate),
+                    )
                     .unwrap();
             }
         }
@@ -301,6 +312,7 @@ struct FileMeta {
     path: String,
     krate: Option<String>,
     deps: Vec<String>,
+    extern_prelude: Vec<String>,
     cfg: CfgOptions,
     edition: Edition,
     env: Env,
@@ -313,10 +325,12 @@ impl From<Fixture> for FileMeta {
         f.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into()));
         f.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into()));
 
+        let deps = f.deps;
         FileMeta {
             path: f.path,
             krate: f.krate,
-            deps: f.deps,
+            extern_prelude: f.extern_prelude.unwrap_or_else(|| deps.clone()),
+            deps,
             cfg,
             edition: f.edition.as_ref().map_or(Edition::CURRENT, |v| Edition::from_str(v).unwrap()),
             env: f.env.into_iter().collect(),
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs
index 565d0c02cf..1dd7783b0e 100644
--- a/crates/base_db/src/input.rs
+++ b/crates/base_db/src/input.rs
@@ -217,6 +217,22 @@ pub struct Env {
 pub struct Dependency {
     pub crate_id: CrateId,
     pub name: CrateName,
+    prelude: bool,
+}
+
+impl Dependency {
+    pub fn new(name: CrateName, crate_id: CrateId) -> Self {
+        Self { name, crate_id, prelude: true }
+    }
+
+    pub fn with_prelude(name: CrateName, crate_id: CrateId, prelude: bool) -> Self {
+        Self { name, crate_id, prelude }
+    }
+
+    /// Whether this dependency is to be added to the depending crate's extern prelude.
+    pub fn is_prelude(&self) -> bool {
+        self.prelude
+    }
 }
 
 impl CrateGraph {
@@ -249,22 +265,21 @@ impl CrateGraph {
     pub fn add_dep(
         &mut self,
         from: CrateId,
-        name: CrateName,
-        to: CrateId,
+        dep: Dependency,
     ) -> Result<(), CyclicDependenciesError> {
         let _p = profile::span("add_dep");
 
         // Check if adding a dep from `from` to `to` creates a cycle. To figure
         // that out, look for a  path in the *opposite* direction, from `to` to
         // `from`.
-        if let Some(path) = self.find_path(&mut FxHashSet::default(), to, from) {
+        if let Some(path) = self.find_path(&mut FxHashSet::default(), dep.crate_id, from) {
             let path = path.into_iter().map(|it| (it, self[it].display_name.clone())).collect();
             let err = CyclicDependenciesError { path };
-            assert!(err.from().0 == from && err.to().0 == to);
+            assert!(err.from().0 == from && err.to().0 == dep.crate_id);
             return Err(err);
         }
 
-        self.arena.get_mut(&from).unwrap().add_dep(name, to);
+        self.arena.get_mut(&from).unwrap().add_dep(dep);
         Ok(())
     }
 
@@ -409,7 +424,7 @@ impl CrateGraph {
                     .get_mut(&std)
                     .unwrap()
                     .dependencies
-                    .push(Dependency { crate_id: cfg_if, name: CrateName::new("cfg_if").unwrap() });
+                    .push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if));
                 true
             }
             _ => false,
@@ -435,8 +450,8 @@ impl CrateId {
 }
 
 impl CrateData {
-    fn add_dep(&mut self, name: CrateName, crate_id: CrateId) {
-        self.dependencies.push(Dependency { crate_id, name })
+    fn add_dep(&mut self, dep: Dependency) {
+        self.dependencies.push(dep)
     }
 }
 
@@ -562,9 +577,15 @@ mod tests {
             Env::default(),
             Default::default(),
         );
-        assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
-        assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok());
-        assert!(graph.add_dep(crate3, CrateName::new("crate1").unwrap(), crate1).is_err());
+        assert!(graph
+            .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+            .is_ok());
+        assert!(graph
+            .add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3))
+            .is_ok());
+        assert!(graph
+            .add_dep(crate3, Dependency::new(CrateName::new("crate1").unwrap(), crate1))
+            .is_err());
     }
 
     #[test]
@@ -588,8 +609,12 @@ mod tests {
             Env::default(),
             Default::default(),
         );
-        assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
-        assert!(graph.add_dep(crate2, CrateName::new("crate2").unwrap(), crate2).is_err());
+        assert!(graph
+            .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+            .is_ok());
+        assert!(graph
+            .add_dep(crate2, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+            .is_err());
     }
 
     #[test]
@@ -622,8 +647,12 @@ mod tests {
             Env::default(),
             Default::default(),
         );
-        assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
-        assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok());
+        assert!(graph
+            .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+            .is_ok());
+        assert!(graph
+            .add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3))
+            .is_ok());
     }
 
     #[test]
@@ -648,14 +677,14 @@ mod tests {
             Default::default(),
         );
         assert!(graph
-            .add_dep(crate1, CrateName::normalize_dashes("crate-name-with-dashes"), crate2)
+            .add_dep(
+                crate1,
+                Dependency::new(CrateName::normalize_dashes("crate-name-with-dashes"), crate2)
+            )
             .is_ok());
         assert_eq!(
             graph[crate1].dependencies,
-            vec![Dependency {
-                crate_id: crate2,
-                name: CrateName::new("crate_name_with_dashes").unwrap()
-            }]
+            vec![Dependency::new(CrateName::new("crate_name_with_dashes").unwrap(), crate2)]
         );
     }
 }
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index d6762d1acf..53ab87005c 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -61,14 +61,17 @@ pub(super) fn collect_defs(
 ) -> DefMap {
     let crate_graph = db.crate_graph();
 
-    if block.is_none() {
-        // populate external prelude
-        for dep in &crate_graph[def_map.krate].dependencies {
-            tracing::debug!("crate dep {:?} -> {:?}", dep.name, dep.crate_id);
-            let dep_def_map = db.crate_def_map(dep.crate_id);
-            def_map
-                .extern_prelude
-                .insert(dep.as_name(), dep_def_map.module_id(dep_def_map.root).into());
+    let mut deps = FxHashMap::default();
+    // populate external prelude and dependency list
+    for dep in &crate_graph[def_map.krate].dependencies {
+        tracing::debug!("crate dep {:?} -> {:?}", dep.name, dep.crate_id);
+        let dep_def_map = db.crate_def_map(dep.crate_id);
+        let dep_root = dep_def_map.module_id(dep_def_map.root);
+
+        deps.insert(dep.as_name(), dep_root.into());
+
+        if dep.is_prelude() && block.is_none() {
+            def_map.extern_prelude.insert(dep.as_name(), dep_root.into());
         }
     }
 
@@ -87,6 +90,7 @@ pub(super) fn collect_defs(
     let mut collector = DefCollector {
         db,
         def_map,
+        deps,
         glob_imports: FxHashMap::default(),
         unresolved_imports: Vec::new(),
         resolved_imports: Vec::new(),
@@ -239,6 +243,7 @@ struct DefData<'a> {
 struct DefCollector<'a> {
     db: &'a dyn DefDatabase,
     def_map: DefMap,
+    deps: FxHashMap<Name, ModuleDefId>,
     glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility)>>,
     unresolved_imports: Vec<ImportDirective>,
     resolved_imports: Vec<ImportDirective>,
@@ -660,7 +665,7 @@ impl DefCollector<'_> {
             self.def_map.edition,
         );
 
-        let res = self.def_map.resolve_name_in_extern_prelude(self.db, &extern_crate.name);
+        let res = self.resolve_extern_crate(&extern_crate.name);
 
         if let Some(ModuleDefId::ModuleId(m)) = res.take_types() {
             if m == self.def_map.module_id(current_module_id) {
@@ -720,13 +725,13 @@ impl DefCollector<'_> {
     fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialResolvedImport {
         tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.edition);
         if import.is_extern_crate {
-            let res = self.def_map.resolve_name_in_extern_prelude(
-                self.db,
-                import
-                    .path
-                    .as_ident()
-                    .expect("extern crate should have been desugared to one-element path"),
-            );
+            let name = import
+                .path
+                .as_ident()
+                .expect("extern crate should have been desugared to one-element path");
+
+            let res = self.resolve_extern_crate(name);
+
             if res.is_none() {
                 PartialResolvedImport::Unresolved
             } else {
@@ -766,6 +771,24 @@ impl DefCollector<'_> {
         }
     }
 
+    fn resolve_extern_crate(&self, name: &Name) -> PerNs {
+        let arc;
+        let root = match self.def_map.block {
+            Some(_) => {
+                arc = self.def_map.crate_root(self.db).def_map(self.db);
+                &*arc
+            }
+            None => &self.def_map,
+        };
+
+        if name == &name!(self) {
+            cov_mark::hit!(extern_crate_self_as);
+            PerNs::types(root.module_id(root.root()).into(), Visibility::Public)
+        } else {
+            self.deps.get(name).map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public))
+        }
+    }
+
     fn record_resolved_import(&mut self, directive: &ImportDirective) {
         let module_id = directive.module_id;
         let import = &directive.import;
@@ -2009,6 +2032,7 @@ mod tests {
         let mut collector = DefCollector {
             db,
             def_map,
+            deps: FxHashMap::default(),
             glob_imports: FxHashMap::default(),
             unresolved_imports: Vec::new(),
             resolved_imports: Vec::new(),
diff --git a/crates/hir_def/src/nameres/path_resolution.rs b/crates/hir_def/src/nameres/path_resolution.rs
index e71805bc8e..4fb81bde46 100644
--- a/crates/hir_def/src/nameres/path_resolution.rs
+++ b/crates/hir_def/src/nameres/path_resolution.rs
@@ -11,7 +11,6 @@
 //! `ReachedFixedPoint` signals about this.
 
 use base_db::Edition;
-use hir_expand::name;
 use hir_expand::name::Name;
 
 use crate::{
@@ -65,11 +64,6 @@ impl DefMap {
         db: &dyn DefDatabase,
         name: &Name,
     ) -> PerNs {
-        if name == &name!(self) {
-            cov_mark::hit!(extern_crate_self_as);
-            return PerNs::types(self.module_id(self.root).into(), Visibility::Public);
-        }
-
         let arc;
         let root = match self.block {
             Some(_) => {
diff --git a/crates/project_model/src/project_json.rs b/crates/project_model/src/project_json.rs
index 5db1aa1b4f..75ad66a5cc 100644
--- a/crates/project_model/src/project_json.rs
+++ b/crates/project_model/src/project_json.rs
@@ -83,9 +83,8 @@ impl ProjectJson {
                         deps: crate_data
                             .deps
                             .into_iter()
-                            .map(|dep_data| Dependency {
-                                crate_id: CrateId(dep_data.krate as u32),
-                                name: dep_data.name,
+                            .map(|dep_data| {
+                                Dependency::new(dep_data.name, CrateId(dep_data.krate as u32))
                             })
                             .collect::<Vec<_>>(),
                         cfg: crate_data.cfg,
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index 6c17492948..153cbc1c84 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -4,7 +4,7 @@
 //! but we can't process `.rlib` and need source code instead. The source code
 //! is typically installed with `rustup component add rust-src` command.
 
-use std::{convert::TryFrom, env, fs, ops, path::PathBuf, process::Command};
+use std::{convert::TryFrom, env, fs, iter, ops, path::PathBuf, process::Command};
 
 use anyhow::{format_err, Result};
 use la_arena::{Arena, Idx};
@@ -39,10 +39,15 @@ impl Sysroot {
         &self.root
     }
 
-    pub fn public_deps(&self) -> impl Iterator<Item = (&'static str, SysrootCrate)> + '_ {
+    pub fn public_deps(&self) -> impl Iterator<Item = (&'static str, SysrootCrate, bool)> + '_ {
         // core is added as a dependency before std in order to
         // mimic rustcs dependency order
-        ["core", "alloc", "std"].iter().filter_map(move |&it| Some((it, self.by_name(it)?)))
+        ["core", "alloc", "std"]
+            .iter()
+            .copied()
+            .zip(iter::repeat(true))
+            .chain(iter::once(("test", false)))
+            .filter_map(move |(name, prelude)| Some((name, self.by_name(name)?, prelude)))
     }
 
     pub fn proc_macro(&self) -> Option<SysrootCrate> {
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index 282116c297..714e2dd301 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -5,7 +5,9 @@
 use std::{collections::VecDeque, convert::TryFrom, fmt, fs, process::Command};
 
 use anyhow::{format_err, Context, Result};
-use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro};
+use base_db::{
+    CrateDisplayName, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, ProcMacro,
+};
 use cfg::{CfgDiff, CfgOptions};
 use paths::{AbsPath, AbsPathBuf};
 use rustc_hash::{FxHashMap, FxHashSet};
@@ -468,9 +470,7 @@ fn project_json_to_crate_graph(
     for (from, krate) in project.crates() {
         if let Some(&from) = crates.get(&from) {
             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)
-                }
+                public_deps.add(from, &mut crate_graph);
                 if krate.is_proc_macro {
                     if let Some(proc_macro) = libproc_macro {
                         add_dep(
@@ -507,7 +507,7 @@ fn cargo_to_crate_graph(
     let mut crate_graph = CrateGraph::default();
     let (public_deps, libproc_macro) = match sysroot {
         Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load),
-        None => (Vec::new(), None),
+        None => (SysrootPublicDeps::default(), None),
     };
 
     let mut cfg_options = CfgOptions::default();
@@ -588,9 +588,7 @@ fn cargo_to_crate_graph(
                     add_dep(&mut crate_graph, *from, name, to);
                 }
             }
-            for (name, krate) in public_deps.iter() {
-                add_dep(&mut crate_graph, *from, name.clone(), *krate);
-            }
+            public_deps.add(*from, &mut crate_graph);
         }
     }
 
@@ -672,9 +670,7 @@ fn detached_files_to_crate_graph(
             Vec::new(),
         );
 
-        for (name, krate) in public_deps.iter() {
-            add_dep(&mut crate_graph, detached_file_crate, name.clone(), *krate);
-        }
+        public_deps.add(detached_file_crate, &mut crate_graph);
     }
     crate_graph
 }
@@ -686,7 +682,7 @@ fn handle_rustc_crates(
     cfg_options: &CfgOptions,
     load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
     pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
-    public_deps: &[(CrateName, CrateId)],
+    public_deps: &SysrootPublicDeps,
     cargo: &CargoWorkspace,
     pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<(CrateId, TargetKind)>>,
 ) {
@@ -726,9 +722,7 @@ fn handle_rustc_crates(
                     );
                     pkg_to_lib_crate.insert(pkg, crate_id);
                     // Add dependencies on core / std / alloc for this crate
-                    for (name, krate) in public_deps.iter() {
-                        add_dep(crate_graph, crate_id, name.clone(), *krate);
-                    }
+                    public_deps.add(crate_id, crate_graph);
                     rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
                 }
             }
@@ -826,12 +820,26 @@ fn add_target_crate_root(
     )
 }
 
+#[derive(Default)]
+struct SysrootPublicDeps {
+    deps: Vec<(CrateName, CrateId, bool)>,
+}
+
+impl SysrootPublicDeps {
+    /// Makes `from` depend on the public sysroot crates.
+    fn add(&self, from: CrateId, crate_graph: &mut CrateGraph) {
+        for (name, krate, prelude) in &self.deps {
+            add_dep_with_prelude(crate_graph, from, name.clone(), *krate, *prelude);
+        }
+    }
+}
+
 fn sysroot_to_crate_graph(
     crate_graph: &mut CrateGraph,
     sysroot: &Sysroot,
     rustc_cfg: Vec<CfgFlag>,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
-) -> (Vec<(CrateName, CrateId)>, Option<CrateId>) {
+) -> (SysrootPublicDeps, Option<CrateId>) {
     let _p = profile::span("sysroot_to_crate_graph");
     let mut cfg_options = CfgOptions::default();
     cfg_options.extend(rustc_cfg);
@@ -865,17 +873,35 @@ fn sysroot_to_crate_graph(
         }
     }
 
-    let public_deps = sysroot
-        .public_deps()
-        .map(|(name, idx)| (CrateName::new(name).unwrap(), sysroot_crates[&idx]))
-        .collect::<Vec<_>>();
+    let public_deps = SysrootPublicDeps {
+        deps: sysroot
+            .public_deps()
+            .map(|(name, idx, prelude)| {
+                (CrateName::new(name).unwrap(), sysroot_crates[&idx], prelude)
+            })
+            .collect::<Vec<_>>(),
+    };
 
     let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
     (public_deps, libproc_macro)
 }
 
 fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
-    if let Err(err) = graph.add_dep(from, name, to) {
+    add_dep_inner(graph, from, Dependency::new(name, to))
+}
+
+fn add_dep_with_prelude(
+    graph: &mut CrateGraph,
+    from: CrateId,
+    name: CrateName,
+    to: CrateId,
+    prelude: bool,
+) {
+    add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
+}
+
+fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
+    if let Err(err) = graph.add_dep(from, dep) {
         tracing::error!("{}", err)
     }
 }


Test patch:
diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs
index ff34b4a9f2..37336ce1e9 100644
--- a/crates/hir_def/src/nameres/tests.rs
+++ b/crates/hir_def/src/nameres/tests.rs
@@ -860,3 +860,33 @@ pub const settings: () = ();
         "#]],
     )
 }
+
+#[test]
+fn non_prelude_deps() {
+    check(
+        r#"
+//- /lib.rs crate:lib deps:dep extern-prelude:
+use dep::Struct;
+//- /dep.rs crate:dep
+pub struct Struct;
+        "#,
+        expect![[r#"
+            crate
+            Struct: _
+        "#]],
+    );
+    check(
+        r#"
+//- /lib.rs crate:lib deps:dep extern-prelude:
+extern crate dep;
+use dep::Struct;
+//- /dep.rs crate:dep
+pub struct Struct;
+        "#,
+        expect![[r#"
+            crate
+            Struct: t v
+            dep: t
+        "#]],
+    );
+}
diff --git a/crates/project_model/src/tests.rs b/crates/project_model/src/tests.rs
index 0e37624f31..647d30605d 100644
--- a/crates/project_model/src/tests.rs
+++ b/crates/project_model/src/tests.rs
@@ -166,6 +166,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -231,6 +232,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -287,6 +289,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 name: CrateName(
                                     "hello_world",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -295,6 +298,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -407,6 +411,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 name: CrateName(
                                     "hello_world",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -415,6 +420,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -527,6 +533,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 name: CrateName(
                                     "hello_world",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -535,6 +542,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -615,6 +623,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -680,6 +689,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -738,6 +748,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 name: CrateName(
                                     "hello_world",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -746,6 +757,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -860,6 +872,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 name: CrateName(
                                     "hello_world",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -868,6 +881,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -982,6 +996,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 name: CrateName(
                                     "hello_world",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -990,6 +1005,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -1061,6 +1077,7 @@ fn cargo_hello_world_project_model() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -1128,6 +1145,7 @@ fn cargo_hello_world_project_model() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -1186,6 +1204,7 @@ fn cargo_hello_world_project_model() {
                                 name: CrateName(
                                     "hello_world",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1194,6 +1213,7 @@ fn cargo_hello_world_project_model() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -1310,6 +1330,7 @@ fn cargo_hello_world_project_model() {
                                 name: CrateName(
                                     "hello_world",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1318,6 +1339,7 @@ fn cargo_hello_world_project_model() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -1434,6 +1456,7 @@ fn cargo_hello_world_project_model() {
                                 name: CrateName(
                                     "hello_world",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1442,6 +1465,7 @@ fn cargo_hello_world_project_model() {
                                 name: CrateName(
                                     "libc",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -1491,6 +1515,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "core",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -1581,6 +1606,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "std",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
@@ -1644,6 +1670,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "core",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1652,6 +1679,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "alloc",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1660,6 +1688,16 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "std",
                                 ),
+                                prelude: true,
+                            },
+                            Dependency {
+                                crate_id: CrateId(
+                                    9,
+                                ),
+                                name: CrateName(
+                                    "test",
+                                ),
+                                prelude: false,
                             },
                         ],
                         proc_macro: [],
@@ -1804,6 +1842,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "alloc",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1812,6 +1851,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "core",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1820,6 +1860,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "panic_abort",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1828,6 +1869,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "panic_unwind",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1836,6 +1878,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "profiler_builtins",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1844,6 +1887,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "std_detect",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1852,6 +1896,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "term",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1860,6 +1905,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "test",
                                 ),
+                                prelude: true,
                             },
                             Dependency {
                                 crate_id: CrateId(
@@ -1868,6 +1914,7 @@ fn rust_project_hello_world_project_model() {
                                 name: CrateName(
                                     "unwind",
                                 ),
+                                prelude: true,
                             },
                         ],
                         proc_macro: [],
diff --git a/crates/test_utils/src/fixture.rs b/crates/test_utils/src/fixture.rs
index 71cc67a9ca..f2013b34c5 100644
--- a/crates/test_utils/src/fixture.rs
+++ b/crates/test_utils/src/fixture.rs
@@ -70,6 +70,7 @@ pub struct Fixture {
     pub text: String,
     pub krate: Option<String>,
     pub deps: Vec<String>,
+    pub extern_prelude: Option<Vec<String>>,
     pub cfg_atoms: Vec<String>,
     pub cfg_key_values: Vec<(String, String)>,
     pub edition: Option<String>,
@@ -171,6 +172,7 @@ impl Fixture {
 
         let mut krate = None;
         let mut deps = Vec::new();
+        let mut extern_prelude = None;
         let mut edition = None;
         let mut cfg_atoms = Vec::new();
         let mut cfg_key_values = Vec::new();
@@ -183,6 +185,14 @@ impl Fixture {
             match key {
                 "crate" => krate = Some(value.to_string()),
                 "deps" => deps = value.split(',').map(|it| it.to_string()).collect(),
+                "extern-prelude" => {
+                    if value.is_empty() {
+                        extern_prelude = Some(Vec::new());
+                    } else {
+                        extern_prelude =
+                            Some(value.split(',').map(|it| it.to_string()).collect::<Vec<_>>());
+                    }
+                }
                 "edition" => edition = Some(value.to_string()),
                 "cfg" => {
                     for entry in value.split(',') {
@@ -204,11 +214,21 @@ impl Fixture {
             }
         }
 
+        for prelude_dep in extern_prelude.iter().flatten() {
+            assert!(
+                deps.contains(prelude_dep),
+                "extern-prelude {:?} must be a subset of deps {:?}",
+                extern_prelude,
+                deps
+            );
+        }
+
         Fixture {
             path,
             text: String::new(),
             krate,
             deps,
+            extern_prelude,
             cfg_atoms,
             cfg_key_values,
             edition,