From d51536c242e7624d9678c78e865a229b4e3678d1 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Wed, 28 Jun 2023 22:34:14 +0900 Subject: [PATCH] Add `library` fixture meta Additionally documents the syntax for fixture meta. --- crates/base-db/src/fixture.rs | 110 +++++++++++++++++---------- crates/base-db/src/input.rs | 6 +- crates/ide/src/moniker.rs | 16 ++-- crates/rust-analyzer/src/cli/scip.rs | 14 ++-- crates/test-utils/src/fixture.rs | 95 ++++++++++++++++++----- 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 @@ pub fn parse_with_proc_macros( 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 @@ pub fn parse_with_proc_macros( 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 @@ pub fn parse_with_proc_macros( 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, } -fn parse_crate(crate_str: String) -> (String, CrateOrigin, Option) { - 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 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) { + // 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 @@ fn deref(&self) -> &str { } } -/// 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, name: Option }, /// Crates that are non member libraries. Library { repo: Option, 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 @@ fn basic() { 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 @@ pub fn 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 fn func$0() {} 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 fn func$0() {} 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 trait MyTrait { 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 trait MyTrait { 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 @@ fn moniker_for_field() { 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 @@ fn no_moniker_for_local() { 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 @@ fn basic() { 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 fn func() {} 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 fn func$0() {} 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 trait MyTrait { 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 trait MyTrait { 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 @@ fn symbol_for_field() { 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 @@ fn local_symbol_for_local() { 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 @@ #[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, + /// Specifies dependencies of this crate. This must be used with `crate` meta. + /// + /// Syntax: `deps:hir-def,ide-assists` pub deps: Vec, + /// 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>, - pub cfg_atoms: Vec, - 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)>, + /// 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, + /// Specifies environment variables. + /// + /// Syntax: `env:PATH=/bin,RUST_LOG=debug` pub env: FxHashMap, + /// 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, + /// 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, + /// Actual file contents. All meta comments are stripped. + pub text: String, } pub struct MiniCore { @@ -178,23 +232,28 @@ pub fn parse(ra_fixture: &str) -> Self { fn parse_meta_line(meta: &str) -> Fixture { assert!(meta.starts_with("//-")); let meta = meta["//-".len()..].trim(); - let components = meta.split_ascii_whitespace().collect::>(); + 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 @@ fn parse_meta_line(meta: &str) -> Fixture { "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 @@ fn parse_meta_line(meta: &str) -> Fixture { krate, deps, extern_prelude, - cfg_atoms, - cfg_key_values, + cfgs, edition, env, introduce_new_source_root, + library, target_data_layout, } }