about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-06-28 15:23:32 +0000
committerbors <bors@rust-lang.org>2023-06-28 15:23:32 +0000
commitae89ca3fbb6c9e499b406e2f318920018365e415 (patch)
tree0cbd6bfdd01c7bceeb4134e06c7bb0c852e379f8
parentac4e046c69e1584b043a86f81c75783592265605 (diff)
parentd51536c242e7624d9678c78e865a229b4e3678d1 (diff)
downloadrust-ae89ca3fbb6c9e499b406e2f318920018365e415.tar.gz
rust-ae89ca3fbb6c9e499b406e2f318920018365e415.zip
Auto merge of #15161 - lowr:patch/fixture-metadata-library, r=Veykril
internal: add `library` fixture meta

Currently, there is no way to specify `CrateOrigin` of a file fixture ([this] might be a bug?). This PR adds `library` meta to explicitly specify the fixture to be `CrateOrigin::Library` and also makes sure crates that belong to a library source root are set `CrateOrigin::Library`.

(`library` isn't really the best name. It essentially means that the crate is outside workspace but `non_workspace_member` feels a bit too long. Suggestions for the better name would be appreciated)

Additionally:
- documents the fixture meta syntax as thoroughly as possible
- refactors relevant code

[this]: https://github.com/rust-lang/rust-analyzer/blob/4b06d3c595a75fd84bfce2b7f2861a913ed6e530/crates/base-db/src/fixture.rs#L450
-rw-r--r--crates/base-db/src/fixture.rs110
-rw-r--r--crates/base-db/src/input.rs6
-rw-r--r--crates/ide/src/moniker.rs16
-rw-r--r--crates/rust-analyzer/src/cli/scip.rs14
-rw-r--r--crates/test-utils/src/fixture.rs95
5 files changed, 164 insertions, 77 deletions
diff --git a/crates/base-db/src/fixture.rs b/crates/base-db/src/fixture.rs
index d3abc3870b7..1aa7b92645c 100644
--- a/crates/base-db/src/fixture.rs
+++ b/crates/base-db/src/fixture.rs
@@ -102,6 +102,8 @@ pub struct ChangeFixture {
     pub change: Change,
 }
 
+const SOURCE_ROOT_PREFIX: &str = "/";
+
 impl ChangeFixture {
     pub fn parse(ra_fixture: &str) -> ChangeFixture {
         Self::parse_with_proc_macros(ra_fixture, Vec::new())
@@ -131,7 +133,6 @@ impl ChangeFixture {
 
         let mut file_set = FileSet::default();
         let mut current_source_root_kind = SourceRootKind::Local;
-        let source_root_prefix = "/".to_string();
         let mut file_id = FileId(0);
         let mut roots = Vec::new();
 
@@ -151,19 +152,23 @@ impl ChangeFixture {
                 entry.text.clone()
             };
 
-            let meta = FileMeta::from(entry);
-            assert!(meta.path.starts_with(&source_root_prefix));
+            let meta = FileMeta::from_fixture(entry, current_source_root_kind);
+            assert!(meta.path.starts_with(SOURCE_ROOT_PREFIX));
             if !meta.deps.is_empty() {
                 assert!(meta.krate.is_some(), "can't specify deps without naming the crate")
             }
 
-            if let Some(kind) = &meta.introduce_new_source_root {
-                let root = match current_source_root_kind {
+            if let Some(kind) = meta.introduce_new_source_root {
+                assert!(
+                    meta.krate.is_some(),
+                    "new_source_root meta doesn't make sense without crate meta"
+                );
+                let prev_kind = mem::replace(&mut current_source_root_kind, kind);
+                let prev_root = match prev_kind {
                     SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
                     SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
                 };
-                roots.push(root);
-                current_source_root_kind = *kind;
+                roots.push(prev_root);
             }
 
             if let Some((krate, origin, version)) = meta.krate {
@@ -185,7 +190,7 @@ impl ChangeFixture {
                     Some(toolchain),
                 );
                 let prev = crates.insert(crate_name.clone(), crate_id);
-                assert!(prev.is_none());
+                assert!(prev.is_none(), "multiple crates with same name: {}", crate_name);
                 for dep in meta.deps {
                     let prelude = meta.extern_prelude.contains(&dep);
                     let dep = CrateName::normalize_dashes(&dep);
@@ -442,51 +447,74 @@ struct FileMeta {
     target_data_layout: Option<String>,
 }
 
-fn parse_crate(crate_str: String) -> (String, CrateOrigin, Option<String>) {
-    if let Some((a, b)) = crate_str.split_once('@') {
-        let (version, origin) = match b.split_once(':') {
-            Some(("CratesIo", data)) => match data.split_once(',') {
-                Some((version, url)) => {
-                    (version, CrateOrigin::Local { repo: Some(url.to_owned()), name: None })
-                }
-                _ => panic!("Bad crates.io parameter: {data}"),
-            },
-            _ => panic!("Bad string for crate origin: {b}"),
-        };
-        (a.to_owned(), origin, Some(version.to_string()))
-    } else {
-        let crate_origin = match LangCrateOrigin::from(&*crate_str) {
-            LangCrateOrigin::Other => CrateOrigin::Local { repo: None, name: None },
-            origin => CrateOrigin::Lang(origin),
-        };
-        (crate_str, crate_origin, None)
-    }
-}
-
-impl From<Fixture> for FileMeta {
-    fn from(f: Fixture) -> FileMeta {
+impl FileMeta {
+    fn from_fixture(f: Fixture, current_source_root_kind: SourceRootKind) -> Self {
         let mut cfg = CfgOptions::default();
-        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()));
+        for (k, v) in f.cfgs {
+            if let Some(v) = v {
+                cfg.insert_key_value(k.into(), v.into());
+            } else {
+                cfg.insert_atom(k.into());
+            }
+        }
+
+        let introduce_new_source_root = f.introduce_new_source_root.map(|kind| match &*kind {
+            "local" => SourceRootKind::Local,
+            "library" => SourceRootKind::Library,
+            invalid => panic!("invalid source root kind '{invalid}'"),
+        });
+        let current_source_root_kind =
+            introduce_new_source_root.unwrap_or(current_source_root_kind);
+
         let deps = f.deps;
-        FileMeta {
+        Self {
             path: f.path,
-            krate: f.krate.map(parse_crate),
+            krate: f.krate.map(|it| parse_crate(it, current_source_root_kind, f.library)),
             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()),
+            edition: f.edition.map_or(Edition::CURRENT, |v| Edition::from_str(&v).unwrap()),
             env: f.env.into_iter().collect(),
-            introduce_new_source_root: f.introduce_new_source_root.map(|kind| match &*kind {
-                "local" => SourceRootKind::Local,
-                "library" => SourceRootKind::Library,
-                invalid => panic!("invalid source root kind '{invalid}'"),
-            }),
+            introduce_new_source_root,
             target_data_layout: f.target_data_layout,
         }
     }
 }
 
+fn parse_crate(
+    crate_str: String,
+    current_source_root_kind: SourceRootKind,
+    explicit_non_workspace_member: bool,
+) -> (String, CrateOrigin, Option<String>) {
+    // syntax:
+    //   "my_awesome_crate"
+    //   "my_awesome_crate@0.0.1,http://example.com"
+    let (name, repo, version) = if let Some((name, remain)) = crate_str.split_once('@') {
+        let (version, repo) =
+            remain.split_once(',').expect("crate meta: found '@' without version and url");
+        (name.to_owned(), Some(repo.to_owned()), Some(version.to_owned()))
+    } else {
+        (crate_str, None, None)
+    };
+
+    let non_workspace_member = explicit_non_workspace_member
+        || matches!(current_source_root_kind, SourceRootKind::Library);
+
+    let origin = match LangCrateOrigin::from(&*name) {
+        LangCrateOrigin::Other => {
+            let name = name.clone();
+            if non_workspace_member {
+                CrateOrigin::Library { repo, name }
+            } else {
+                CrateOrigin::Local { repo, name: Some(name) }
+            }
+        }
+        origin => CrateOrigin::Lang(origin),
+    };
+
+    (name, origin, version)
+}
+
 // Identity mapping
 #[derive(Debug)]
 struct IdentityProcMacroExpander;
diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index f2e523675bc..c47799f1320 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -138,12 +138,12 @@ impl ops::Deref for CrateName {
     }
 }
 
-/// Origin of the crates. It is used in emitting monikers.
+/// Origin of the crates.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum CrateOrigin {
-    /// Crates that are from the rustc workspace
+    /// Crates that are from the rustc workspace.
     Rustc { name: String },
-    /// Crates that are workspace members,
+    /// Crates that are workspace members.
     Local { repo: Option<String>, name: Option<String> },
     /// Crates that are non member libraries.
     Library { repo: Option<String>, name: String },
diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs
index 0d57e63d29c..d486a794e13 100644
--- a/crates/ide/src/moniker.rs
+++ b/crates/ide/src/moniker.rs
@@ -320,7 +320,7 @@ use foo::module::func;
 fn main() {
     func$0();
 }
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
 pub mod module {
     pub fn func() {}
 }
@@ -336,7 +336,7 @@ use foo::module::func;
 fn main() {
     func();
 }
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
 pub mod module {
     pub fn func$0() {}
 }
@@ -351,7 +351,7 @@ pub mod module {
     fn moniker_for_trait() {
         check_moniker(
             r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
 pub mod module {
     pub trait MyTrait {
         pub fn func$0() {}
@@ -368,7 +368,7 @@ pub mod module {
     fn moniker_for_trait_constant() {
         check_moniker(
             r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
 pub mod module {
     pub trait MyTrait {
         const MY_CONST$0: u8;
@@ -385,7 +385,7 @@ pub mod module {
     fn moniker_for_trait_type() {
         check_moniker(
             r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
 pub mod module {
     pub trait MyTrait {
         type MyType$0;
@@ -402,7 +402,7 @@ pub mod module {
     fn moniker_for_trait_impl_function() {
         check_moniker(
             r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
 pub mod module {
     pub trait MyTrait {
         pub fn func() {}
@@ -430,7 +430,7 @@ use foo::St;
 fn main() {
     let x = St { a$0: 2 };
 }
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
 pub struct St {
     pub a: i32,
 }
@@ -450,7 +450,7 @@ use foo::module::func;
 fn main() {
     func();
 }
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
 pub mod module {
     pub fn func() {
         let x$0 = 2;
diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs
index 7ad68527d27..3dd2dc69a18 100644
--- a/crates/rust-analyzer/src/cli/scip.rs
+++ b/crates/rust-analyzer/src/cli/scip.rs
@@ -324,7 +324,7 @@ use foo::example_mod::func;
 fn main() {
     func$0();
 }
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
 pub mod example_mod {
     pub fn func() {}
 }
@@ -337,7 +337,7 @@ pub mod example_mod {
     fn symbol_for_trait() {
         check_symbol(
             r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
 pub mod module {
     pub trait MyTrait {
         pub fn func$0() {}
@@ -352,7 +352,7 @@ pub mod module {
     fn symbol_for_trait_constant() {
         check_symbol(
             r#"
-    //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
     pub mod module {
         pub trait MyTrait {
             const MY_CONST$0: u8;
@@ -367,7 +367,7 @@ pub mod module {
     fn symbol_for_trait_type() {
         check_symbol(
             r#"
-    //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
     pub mod module {
         pub trait MyTrait {
             type MyType$0;
@@ -383,7 +383,7 @@ pub mod module {
     fn symbol_for_trait_impl_function() {
         check_symbol(
             r#"
-    //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
     pub mod module {
         pub trait MyTrait {
             pub fn func() {}
@@ -410,7 +410,7 @@ pub mod module {
     fn main() {
         let x = St { a$0: 2 };
     }
-    //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
     pub struct St {
         pub a: i32,
     }
@@ -428,7 +428,7 @@ pub mod module {
     fn main() {
         func();
     }
-    //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+    //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
     pub mod module {
         pub fn func() {
             let x$0 = 2;
diff --git a/crates/test-utils/src/fixture.rs b/crates/test-utils/src/fixture.rs
index 602baed3707..75e7a3fec00 100644
--- a/crates/test-utils/src/fixture.rs
+++ b/crates/test-utils/src/fixture.rs
@@ -47,13 +47,10 @@
 //! ```
 //!
 //! Metadata allows specifying all settings and variables
-//! that are available in a real rust project:
-//! - crate names via `crate:cratename`
-//! - dependencies via `deps:dep1,dep2`
-//! - configuration settings via `cfg:dbg=false,opt_level=2`
-//! - environment variables via `env:PATH=/bin,RUST_LOG=debug`
+//! that are available in a real rust project. See [`Fixture`]
+//! for the syntax.
 //!
-//! Example using all available metadata:
+//! Example using some available metadata:
 //! ```
 //! "
 //! //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
@@ -68,17 +65,74 @@ use stdx::trim_indent;
 
 #[derive(Debug, Eq, PartialEq)]
 pub struct Fixture {
+    /// Specifies the path for this file. It must start with "/".
     pub path: String,
-    pub text: String,
+    /// Defines a new crate and make this file its root module.
+    ///
+    /// Version and repository URL of the crate can optionally be specified; if
+    /// either one is specified, the other must also be specified.
+    ///
+    /// Syntax:
+    /// - `crate:my_awesome_lib`
+    /// - `crate:my_awesome_lib@0.0.1,https://example.com/repo.git`
     pub krate: Option<String>,
+    /// Specifies dependencies of this crate. This must be used with `crate` meta.
+    ///
+    /// Syntax: `deps:hir-def,ide-assists`
     pub deps: Vec<String>,
+    /// Limits crates in the extern prelude. The set of crate names must be a
+    /// subset of `deps`. This must be used with `crate` meta.
+    ///
+    /// If this is not specified, all the dependencies will be in the extern prelude.
+    ///
+    /// Syntax: `extern-prelude:hir-def,ide-assists`
     pub extern_prelude: Option<Vec<String>>,
-    pub cfg_atoms: Vec<String>,
-    pub cfg_key_values: Vec<(String, String)>,
+    /// Specifies configuration options to be enabled. Options may have associated
+    /// values.
+    ///
+    /// Syntax: `cfg:test,dbg=false,opt_level=2`
+    pub cfgs: Vec<(String, Option<String>)>,
+    /// Specifies the edition of this crate. This must be used with `crate` meta. If
+    /// this is not specified, ([`base_db::input::Edition::CURRENT`]) will be used.
+    /// This must be used with `crate` meta.
+    ///
+    /// Syntax: `edition:2021`
     pub edition: Option<String>,
+    /// Specifies environment variables.
+    ///
+    /// Syntax: `env:PATH=/bin,RUST_LOG=debug`
     pub env: FxHashMap<String, String>,
+    /// Introduces a new [source root](base_db::input::SourceRoot). This file **and
+    /// the following files** will belong the new source root. This must be used
+    /// with `crate` meta.
+    ///
+    /// Use this if you want to test something that uses `SourceRoot::is_library()`
+    /// to check editability.
+    ///
+    /// Note that files before the first fixture with `new_source_root` meta will
+    /// belong to an implicitly defined local source root.
+    ///
+    /// Syntax:
+    /// - `new_source_root:library`
+    /// - `new_source_root:local`
     pub introduce_new_source_root: Option<String>,
+    /// Explicitly declares this crate as a library outside current workspace. This
+    /// must be used with `crate` meta.
+    ///
+    /// This is implied if this file belongs to a library source root.
+    ///
+    /// Use this if you want to test something that checks if a crate is a workspace
+    /// member via [`CrateOrigin`](base_db::input::CrateOrigin).
+    ///
+    /// Syntax: `library`
+    pub library: bool,
+    /// Specifies LLVM data layout to be used.
+    ///
+    /// You probably don't want to manually specify this. See LLVM manual for the
+    /// syntax, if you must: https://llvm.org/docs/LangRef.html#data-layout
     pub target_data_layout: Option<String>,
+    /// Actual file contents. All meta comments are stripped.
+    pub text: String,
 }
 
 pub struct MiniCore {
@@ -178,23 +232,28 @@ impl FixtureWithProjectMeta {
     fn parse_meta_line(meta: &str) -> Fixture {
         assert!(meta.starts_with("//-"));
         let meta = meta["//-".len()..].trim();
-        let components = meta.split_ascii_whitespace().collect::<Vec<_>>();
+        let mut components = meta.split_ascii_whitespace();
 
-        let path = components[0].to_string();
+        let path = components.next().expect("fixture meta must start with a path").to_string();
         assert!(path.starts_with('/'), "fixture path does not start with `/`: {path:?}");
 
         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();
+        let mut cfgs = Vec::new();
         let mut env = FxHashMap::default();
         let mut introduce_new_source_root = None;
+        let mut library = false;
         let mut target_data_layout = Some(
             "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128".to_string(),
         );
-        for component in components[1..].iter() {
+        for component in components {
+            if component == "library" {
+                library = true;
+                continue;
+            }
+
             let (key, value) =
                 component.split_once(':').unwrap_or_else(|| panic!("invalid meta line: {meta:?}"));
             match key {
@@ -212,8 +271,8 @@ impl FixtureWithProjectMeta {
                 "cfg" => {
                     for entry in value.split(',') {
                         match entry.split_once('=') {
-                            Some((k, v)) => cfg_key_values.push((k.to_string(), v.to_string())),
-                            None => cfg_atoms.push(entry.to_string()),
+                            Some((k, v)) => cfgs.push((k.to_string(), Some(v.to_string()))),
+                            None => cfgs.push((entry.to_string(), None)),
                         }
                     }
                 }
@@ -243,11 +302,11 @@ impl FixtureWithProjectMeta {
             krate,
             deps,
             extern_prelude,
-            cfg_atoms,
-            cfg_key_values,
+            cfgs,
             edition,
             env,
             introduce_new_source_root,
+            library,
             target_data_layout,
         }
     }