diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs index 7f43c29712..3dc86ca2da 100644 --- a/crates/ra_db/src/fixture.rs +++ b/crates/ra_db/src/fixture.rs @@ -61,7 +61,14 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId }; let mut crate_graph = CrateGraph::default(); - crate_graph.add_crate_root(file_id, meta.edition, meta.krate, meta.cfg, meta.env); + crate_graph.add_crate_root( + file_id, + meta.edition, + meta.krate, + meta.cfg, + meta.env, + Default::default(), + ); crate_graph } else { let mut crate_graph = CrateGraph::default(); @@ -71,6 +78,7 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId None, CfgOptions::default(), Env::default(), + Default::default(), ); crate_graph }; @@ -119,6 +127,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option Option, pub cfg_options: CfgOptions, pub env: Env, + pub extern_source: ExternSource, pub dependencies: Vec, } @@ -122,11 +123,22 @@ pub enum Edition { Edition2015, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ExternSourceId(pub u32); + #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct Env { entries: FxHashMap, } +// FIXME: Redesign vfs for solve the following limitation ? +// Note: Some env variables (e.g. OUT_DIR) are located outside of the +// crate. We store a map to allow remap it to ExternSourceId +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct ExternSource { + extern_paths: FxHashMap, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Dependency { pub crate_id: CrateId, @@ -141,6 +153,7 @@ impl CrateGraph { display_name: Option, cfg_options: CfgOptions, env: Env, + extern_source: ExternSource, ) -> CrateId { let data = CrateData { root_file_id: file_id, @@ -148,6 +161,7 @@ impl CrateGraph { display_name, cfg_options, env, + extern_source, dependencies: Vec::new(), }; let crate_id = CrateId(self.arena.len() as u32); @@ -271,6 +285,27 @@ impl Env { } } +impl ExternSource { + pub fn extern_path(&self, path: &str) -> Option<(ExternSourceId, RelativePathBuf)> { + self.extern_paths.iter().find_map(|(root_path, id)| { + if path.starts_with(root_path) { + let mut rel_path = &path[root_path.len()..]; + if rel_path.starts_with("/") { + rel_path = &rel_path[1..]; + } + let rel_path = RelativePathBuf::from_path(rel_path).ok()?; + Some((id.clone(), rel_path)) + } else { + None + } + }) + } + + pub fn set_extern_path(&mut self, root_path: &str, root: ExternSourceId) { + self.extern_paths.insert(root_path.to_owned(), root); + } +} + #[derive(Debug)] pub struct ParseEditionError { invalid_input: String, @@ -300,6 +335,7 @@ mod tests { None, CfgOptions::default(), Env::default(), + Default::default(), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -307,6 +343,7 @@ mod tests { None, CfgOptions::default(), Env::default(), + Default::default(), ); let crate3 = graph.add_crate_root( FileId(3u32), @@ -314,6 +351,7 @@ mod tests { None, CfgOptions::default(), 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()); @@ -329,6 +367,7 @@ mod tests { None, CfgOptions::default(), Env::default(), + Default::default(), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -336,6 +375,7 @@ mod tests { None, CfgOptions::default(), Env::default(), + Default::default(), ); let crate3 = graph.add_crate_root( FileId(3u32), @@ -343,6 +383,7 @@ mod tests { None, CfgOptions::default(), 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()); @@ -357,6 +398,7 @@ mod tests { None, CfgOptions::default(), Env::default(), + Default::default(), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -364,6 +406,7 @@ mod tests { None, CfgOptions::default(), Env::default(), + Default::default(), ); assert!(graph .add_dep(crate1, CrateName::normalize_dashes("crate-name-with-dashes"), crate2) diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs index fb002d717d..d500d5e85b 100644 --- a/crates/ra_db/src/lib.rs +++ b/crates/ra_db/src/lib.rs @@ -11,7 +11,8 @@ use ra_syntax::{ast, Parse, SourceFile, TextRange, TextUnit}; pub use crate::{ cancellation::Canceled, input::{ - CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, SourceRoot, SourceRootId, + CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource, ExternSourceId, + FileId, SourceRoot, SourceRootId, }, }; pub use relative_path::{RelativePath, RelativePathBuf}; @@ -87,6 +88,12 @@ pub trait FileLoader { fn resolve_relative_path(&self, anchor: FileId, relative_path: &RelativePath) -> Option; fn relevant_crates(&self, file_id: FileId) -> Arc>; + + fn resolve_extern_path( + &self, + extern_id: ExternSourceId, + relative_path: &RelativePath, + ) -> Option; } /// Database which stores all significant input facts: source code and project @@ -164,4 +171,13 @@ impl FileLoader for FileLoaderDelegate<&'_ T> { let source_root = self.0.file_source_root(file_id); self.0.source_root_crates(source_root) } + + fn resolve_extern_path( + &self, + extern_id: ExternSourceId, + relative_path: &RelativePath, + ) -> Option { + let source_root = self.0.source_root(SourceRootId(extern_id.0)); + source_root.file_by_relative_path(&relative_path) + } } diff --git a/crates/ra_hir_def/src/test_db.rs b/crates/ra_hir_def/src/test_db.rs index 1568820e9a..0756916a80 100644 --- a/crates/ra_hir_def/src/test_db.rs +++ b/crates/ra_hir_def/src/test_db.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::db::DefDatabase; -use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; +use ra_db::{salsa, CrateId, ExternSourceId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; #[salsa::database( ra_db::SourceDatabaseExtStorage, @@ -52,6 +52,14 @@ impl FileLoader for TestDB { fn relevant_crates(&self, file_id: FileId) -> Arc> { FileLoaderDelegate(self).relevant_crates(file_id) } + + fn resolve_extern_path( + &self, + extern_id: ExternSourceId, + relative_path: &RelativePath, + ) -> Option { + FileLoaderDelegate(self).resolve_extern_path(extern_id, relative_path) + } } impl TestDB { diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs index 3f60b1cca2..a90007f264 100644 --- a/crates/ra_hir_expand/src/builtin_macro.rs +++ b/crates/ra_hir_expand/src/builtin_macro.rs @@ -90,15 +90,15 @@ register_builtin! { (line, Line) => line_expand, (stringify, Stringify) => stringify_expand, (format_args, FormatArgs) => format_args_expand, - (env, Env) => env_expand, - (option_env, OptionEnv) => option_env_expand, // format_args_nl only differs in that it adds a newline in the end, // so we use the same stub expansion for now (format_args_nl, FormatArgsNl) => format_args_expand, EAGER: (concat, Concat) => concat_expand, - (include, Include) => include_expand + (include, Include) => include_expand, + (env, Env) => env_expand, + (option_env, OptionEnv) => option_env_expand } fn line_expand( @@ -137,31 +137,6 @@ fn stringify_expand( Ok(expanded) } -fn env_expand( - _db: &dyn AstDatabase, - _id: LazyMacroId, - _tt: &tt::Subtree, -) -> Result { - // dummy implementation for type-checking purposes - // we cannot use an empty string here, because for - // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become - // `include!("foo.rs"), which maybe infinite loop - let expanded = quote! { "__RA_UNIMPLEMENTATED__" }; - - Ok(expanded) -} - -fn option_env_expand( - _db: &dyn AstDatabase, - _id: LazyMacroId, - _tt: &tt::Subtree, -) -> Result { - // dummy implementation for type-checking purposes - let expanded = quote! { std::option::Option::None::<&str> }; - - Ok(expanded) -} - fn column_expand( _db: &dyn AstDatabase, _id: LazyMacroId, @@ -278,14 +253,29 @@ fn concat_expand( fn relative_file(db: &dyn AstDatabase, call_id: MacroCallId, path: &str) -> Option { let call_site = call_id.as_file().original_file(db); - let path = RelativePath::new(&path); - let res = db.resolve_relative_path(call_site, &path)?; - // Prevent include itself - if res == call_site { - return None; + // Handle trivial case + if let Some(res) = db.resolve_relative_path(call_site, &RelativePath::new(&path)) { + // Prevent include itself + return if res == call_site { None } else { Some(res) }; } - Some(res) + + // Extern paths ? + let krate = db.relevant_crates(call_site).get(0)?.clone(); + let (extern_source_id, relative_file) = + db.crate_graph()[krate].extern_source.extern_path(path)?; + + db.resolve_extern_path(extern_source_id, &relative_file) +} + +fn parse_string(tt: &tt::Subtree) -> Result { + tt.token_trees + .get(0) + .and_then(|tt| match tt { + tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it), + _ => None, + }) + .ok_or_else(|| mbe::ExpandError::ConversionError) } fn include_expand( @@ -293,15 +283,7 @@ fn include_expand( arg_id: EagerMacroId, tt: &tt::Subtree, ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { - let path = tt - .token_trees - .get(0) - .and_then(|tt| match tt { - tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it), - _ => None, - }) - .ok_or_else(|| mbe::ExpandError::ConversionError)?; - + let path = parse_string(tt)?; let file_id = relative_file(db, arg_id.into(), &path).ok_or_else(|| mbe::ExpandError::ConversionError)?; @@ -314,12 +296,58 @@ fn include_expand( Ok((res, FragmentKind::Items)) } +fn get_env_inner(db: &dyn AstDatabase, arg_id: EagerMacroId, key: &str) -> Option { + let call_id: MacroCallId = arg_id.into(); + let original_file = call_id.as_file().original_file(db); + + let krate = db.relevant_crates(original_file).get(0)?.clone(); + db.crate_graph()[krate].env.get(key) +} + +fn env_expand( + db: &dyn AstDatabase, + arg_id: EagerMacroId, + tt: &tt::Subtree, +) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { + let key = parse_string(tt)?; + + // FIXME: + // If the environment variable is not defined int rustc, then a compilation error will be emitted. + // We might do the same if we fully support all other stuffs. + // But for now on, we should return some dummy string for better type infer purpose. + // However, we cannot use an empty string here, because for + // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become + // `include!("foo.rs"), which might go to infinite loop + let s = get_env_inner(db, arg_id, &key).unwrap_or("__RA_UNIMPLEMENTATED__".to_string()); + let expanded = quote! { #s }; + + Ok((expanded, FragmentKind::Expr)) +} + +fn option_env_expand( + db: &dyn AstDatabase, + arg_id: EagerMacroId, + tt: &tt::Subtree, +) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { + let key = parse_string(tt)?; + let expanded = match get_env_inner(db, arg_id, &key) { + None => quote! { std::option::Option::None::<&str> }, + Some(s) => quote! { std::option::Some(#s) }, + }; + + Ok((expanded, FragmentKind::Expr)) +} + #[cfg(test)] mod tests { use super::*; - use crate::{name::AsName, test_db::TestDB, AstNode, MacroCallId, MacroCallKind, MacroCallLoc}; + use crate::{ + name::AsName, test_db::TestDB, AstNode, EagerCallLoc, MacroCallId, MacroCallKind, + MacroCallLoc, + }; use ra_db::{fixture::WithFixture, SourceDatabase}; use ra_syntax::ast::NameOwner; + use std::sync::Arc; fn expand_builtin_macro(ra_fixture: &str) -> String { let (db, file_id) = TestDB::with_single_file(&ra_fixture); @@ -330,27 +358,61 @@ mod tests { let ast_id_map = db.ast_id_map(file_id.into()); let expander = find_by_name(¯o_calls[0].name().unwrap().as_name()).unwrap(); - let expander = expander.left().unwrap(); - // the first one should be a macro_rules - let def = MacroDefId { - krate: Some(CrateId(0)), - ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), - kind: MacroDefKind::BuiltIn(expander), + let file_id = match expander { + Either::Left(expander) => { + // the first one should be a macro_rules + let def = MacroDefId { + krate: Some(CrateId(0)), + ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), + kind: MacroDefKind::BuiltIn(expander), + }; + + let loc = MacroCallLoc { + def, + kind: MacroCallKind::FnLike(AstId::new( + file_id.into(), + ast_id_map.ast_id(¯o_calls[1]), + )), + }; + + let id: MacroCallId = db.intern_macro(loc).into(); + id.as_file() + } + Either::Right(expander) => { + // the first one should be a macro_rules + let def = MacroDefId { + krate: Some(CrateId(0)), + ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), + kind: MacroDefKind::BuiltInEager(expander), + }; + + let args = macro_calls[1].token_tree().unwrap(); + let parsed_args = mbe::ast_to_token_tree(&args).unwrap().0; + + let arg_id = db.intern_eager_expansion({ + EagerCallLoc { + def, + fragment: FragmentKind::Expr, + subtree: Arc::new(parsed_args.clone()), + file_id: file_id.into(), + } + }); + + let (subtree, fragment) = expander.expand(&db, arg_id, &parsed_args).unwrap(); + let eager = EagerCallLoc { + def, + fragment, + subtree: Arc::new(subtree), + file_id: file_id.into(), + }; + + let id: MacroCallId = db.intern_eager_expansion(eager.into()).into(); + id.as_file() + } }; - let loc = MacroCallLoc { - def, - kind: MacroCallKind::FnLike(AstId::new( - file_id.into(), - ast_id_map.ast_id(¯o_calls[1]), - )), - }; - - let id: MacroCallId = db.intern_macro(loc).into(); - let parsed = db.parse_or_expand(id.as_file()).unwrap(); - - parsed.text().to_string() + db.parse_or_expand(file_id).unwrap().to_string() } #[test] diff --git a/crates/ra_hir_expand/src/test_db.rs b/crates/ra_hir_expand/src/test_db.rs index 918736e2a1..c1fb762deb 100644 --- a/crates/ra_hir_expand/src/test_db.rs +++ b/crates/ra_hir_expand/src/test_db.rs @@ -5,7 +5,7 @@ use std::{ sync::{Arc, Mutex}, }; -use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; +use ra_db::{salsa, CrateId, ExternSourceId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; #[salsa::database( ra_db::SourceDatabaseExtStorage, @@ -51,4 +51,11 @@ impl FileLoader for TestDB { fn relevant_crates(&self, file_id: FileId) -> Arc> { FileLoaderDelegate(self).relevant_crates(file_id) } + fn resolve_extern_path( + &self, + anchor: ExternSourceId, + relative_path: &RelativePath, + ) -> Option { + FileLoaderDelegate(self).resolve_extern_path(anchor, relative_path) + } } diff --git a/crates/ra_hir_ty/src/test_db.rs b/crates/ra_hir_ty/src/test_db.rs index c794f7b847..0be2fea4b8 100644 --- a/crates/ra_hir_ty/src/test_db.rs +++ b/crates/ra_hir_ty/src/test_db.rs @@ -67,6 +67,13 @@ impl FileLoader for TestDB { fn relevant_crates(&self, file_id: FileId) -> Arc> { FileLoaderDelegate(self).relevant_crates(file_id) } + fn resolve_extern_path( + &self, + extern_id: ra_db::ExternSourceId, + relative_path: &RelativePath, + ) -> Option { + FileLoaderDelegate(self).resolve_extern_path(extern_id, relative_path) + } } impl TestDB { diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs index ffa78b0464..32457bbf7a 100644 --- a/crates/ra_hir_ty/src/tests/macros.rs +++ b/crates/ra_hir_ty/src/tests/macros.rs @@ -549,6 +549,26 @@ fn main() { ); } +#[test] +fn infer_builtin_macros_env() { + assert_snapshot!( + infer(r#" +//- /main.rs env:foo=bar +#[rustc_builtin_macro] +macro_rules! env {() => {}} + +fn main() { + let x = env!("foo"); +} +"#), + @r###" + ![0; 5) '"bar"': &str + [88; 116) '{ ...o"); }': () + [98; 99) 'x': &str + "### + ); +} + #[test] fn infer_derive_clone_simple() { let (db, pos) = TestDB::with_position( diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 9f45003d33..015fae1956 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -212,6 +212,7 @@ impl Analysis { None, cfg_options, Env::default(), + Default::default(), ); change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text)); change.set_crate_graph(crate_graph); diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs index 90f84b052b..25816cf6fc 100644 --- a/crates/ra_ide/src/mock_analysis.rs +++ b/crates/ra_ide/src/mock_analysis.rs @@ -102,6 +102,7 @@ impl MockAnalysis { None, cfg_options, Env::default(), + Default::default(), )); } else if path.ends_with("/lib.rs") { let crate_name = path.parent().unwrap().file_name().unwrap(); @@ -111,6 +112,7 @@ impl MockAnalysis { Some(crate_name.to_owned()), cfg_options, Env::default(), + Default::default(), ); if let Some(root_crate) = root_crate { crate_graph diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs index b73cefd977..76d130b9b9 100644 --- a/crates/ra_ide/src/parent_module.rs +++ b/crates/ra_ide/src/parent_module.rs @@ -136,6 +136,7 @@ mod tests { None, CfgOptions::default(), Env::default(), + Default::default(), ); let mut change = AnalysisChange::new(); change.set_crate_graph(crate_graph); diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs index 6bcccc8487..fc1b19def0 100644 --- a/crates/ra_ide_db/src/lib.rs +++ b/crates/ra_ide_db/src/lib.rs @@ -55,6 +55,13 @@ impl FileLoader for RootDatabase { fn relevant_crates(&self, file_id: FileId) -> Arc> { FileLoaderDelegate(self).relevant_crates(file_id) } + fn resolve_extern_path( + &self, + extern_id: ra_db::ExternSourceId, + relative_path: &RelativePath, + ) -> Option { + FileLoaderDelegate(self).resolve_extern_path(extern_id, relative_path) + } } impl salsa::Database for RootDatabase { diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 37845ca56f..a6274709d5 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -14,7 +14,7 @@ use std::{ use anyhow::{bail, Context, Result}; use ra_cfg::CfgOptions; -use ra_db::{CrateGraph, CrateName, Edition, Env, FileId}; +use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId}; use rustc_hash::FxHashMap; use serde_json::from_reader; @@ -162,6 +162,7 @@ impl ProjectWorkspace { pub fn to_crate_graph( &self, default_cfg_options: &CfgOptions, + outdirs: &FxHashMap, load: &mut dyn FnMut(&Path) -> Option, ) -> CrateGraph { let mut crate_graph = CrateGraph::default(); @@ -185,6 +186,8 @@ impl ProjectWorkspace { } opts }; + + // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env crates.insert( crate_id, crate_graph.add_crate_root( @@ -194,6 +197,7 @@ impl ProjectWorkspace { None, cfg_options, Env::default(), + Default::default(), ), ); } @@ -231,12 +235,20 @@ impl ProjectWorkspace { opts }; + let mut env = Env::default(); + let mut extern_source = ExternSource::default(); + if let Some((id, path)) = outdirs.get(krate.name(&sysroot)) { + env.set("OUT_DIR", path.clone()); + extern_source.set_extern_path(&path, *id); + } + let crate_id = crate_graph.add_crate_root( file_id, Edition::Edition2018, Some(krate.name(&sysroot).to_string()), cfg_options, - Env::default(), + env, + extern_source, ); sysroot_crates.insert(krate, crate_id); } @@ -275,12 +287,19 @@ impl ProjectWorkspace { opts.insert_features(pkg.features(&cargo).iter().map(Into::into)); opts }; + let mut env = Env::default(); + let mut extern_source = ExternSource::default(); + if let Some((id, path)) = outdirs.get(pkg.name(&cargo)) { + env.set("OUT_DIR", path.clone()); + extern_source.set_extern_path(&path, *id); + } let crate_id = crate_graph.add_crate_root( file_id, edition, Some(pkg.name(&cargo).to_string()), cfg_options, - Env::default(), + env, + extern_source, ); if tgt.kind(&cargo) == TargetKind::Lib { lib_tgt = Some(crate_id); diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index 5df29a383d..2ce69c9b30 100644 --- a/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/crates/rust-analyzer/src/cli/load_cargo.rs @@ -52,7 +52,10 @@ pub(crate) fn load_cargo( opts }; - let crate_graph = ws.to_crate_graph(&default_cfg_options, &mut |path: &Path| { + // FIXME: outdirs? + let outdirs = FxHashMap::default(); + + let crate_graph = ws.to_crate_graph(&default_cfg_options, &outdirs, &mut |path: &Path| { let vfs_file = vfs.load(path); log::debug!("vfs file {:?} -> {:?}", path, vfs_file); vfs_file.map(vfs_file_to_id) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 3314269ece..a8bf29ddf0 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -44,6 +44,9 @@ pub struct ServerConfig { /// Fine grained feature flags to disable specific features. pub feature_flags: FxHashMap, + /// Fine grained controls for additional `OUT_DIR` env variables + pub additional_out_dirs: FxHashMap, + pub rustfmt_args: Vec, /// Cargo feature configurations. @@ -64,6 +67,7 @@ impl Default for ServerConfig { cargo_watch_all_targets: true, with_sysroot: true, feature_flags: FxHashMap::default(), + additional_out_dirs: FxHashMap::default(), cargo_features: Default::default(), rustfmt_args: Vec::new(), } diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index f9de712a00..4f7aac7540 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -196,6 +196,7 @@ pub fn main_loop( Watch(!config.use_client_watching), options, feature_flags, + config.additional_out_dirs, ) }; diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs index 1ddc3c1a5e..9ef368529e 100644 --- a/crates/rust-analyzer/src/world.rs +++ b/crates/rust-analyzer/src/world.rs @@ -26,6 +26,8 @@ use crate::{ vfs_glob::{Glob, RustPackageFilterBuilder}, LspError, Result, }; +use ra_db::ExternSourceId; +use rustc_hash::{FxHashMap, FxHashSet}; #[derive(Debug, Clone)] pub struct Options { @@ -78,6 +80,7 @@ impl WorldState { watch: Watch, options: Options, feature_flags: FeatureFlags, + additional_out_dirs: FxHashMap, ) -> WorldState { let mut change = AnalysisChange::new(); @@ -99,6 +102,19 @@ impl WorldState { RootEntry::new(pkg_root.path().clone(), filter.into_vfs_filter()) })); } + + let extern_dirs: FxHashSet<_> = + additional_out_dirs.iter().map(|(_, path)| (PathBuf::from(path))).collect(); + let mut extern_source_roots = FxHashMap::default(); + + roots.extend(additional_out_dirs.iter().map(|(_, path)| { + let mut filter = RustPackageFilterBuilder::default().set_member(false); + for glob in exclude_globs.iter() { + filter = filter.exclude(glob.clone()); + } + RootEntry::new(PathBuf::from(&path), filter.into_vfs_filter()) + })); + let (task_sender, task_receiver) = unbounded(); let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); @@ -108,6 +124,11 @@ impl WorldState { let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it)); change.add_root(SourceRootId(r.0), is_local); change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string()); + + // FIXME: add path2root in vfs to simpily this logic + if extern_dirs.contains(&vfs_root_path) { + extern_source_roots.insert(vfs_root_path, ExternSourceId(r.0)); + } } // FIXME: Read default cfgs from config @@ -125,11 +146,20 @@ impl WorldState { vfs_file.map(|f| FileId(f.0)) }; - workspaces.iter().map(|ws| ws.to_crate_graph(&default_cfg_options, &mut load)).for_each( - |graph| { + let mut outdirs = FxHashMap::default(); + for (name, path) in additional_out_dirs { + let path = PathBuf::from(&path); + if let Some(id) = extern_source_roots.get(&path) { + outdirs.insert(name, (id.clone(), path.to_string_lossy().replace("\\", "/"))); + } + } + + workspaces + .iter() + .map(|ws| ws.to_crate_graph(&default_cfg_options, &outdirs, &mut load)) + .for_each(|graph| { crate_graph.extend(graph); - }, - ); + }); change.set_crate_graph(crate_graph); // FIXME: Figure out the multi-workspace situation diff --git a/editors/code/package.json b/editors/code/package.json index 5128854545..1fe8e9f8ae 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -224,6 +224,11 @@ "default": true, "description": "Whether to ask for permission before downloading any files from the Internet" }, + "rust-analyzer.additionalOutDirs": { + "type": "object", + "default": {}, + "markdownDescription": "Fine grained controls for OUT_DIR `env!(\"OUT_DIR\")` variable. e.g. `{\"foo\":\"/path/to/foo\"}`, " + }, "rust-analyzer.serverPath": { "type": [ "null", diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 540f7c9ea7..6ce3b9235c 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -37,6 +37,7 @@ export async function createClient(config: Config, serverPath: string): Promise< excludeGlobs: config.excludeGlobs, useClientWatching: config.useClientWatching, featureFlags: config.featureFlags, + additionalOutDirs: config.additionalOutDirs, withSysroot: config.withSysroot, cargoFeatures: config.cargoFeatures, rustfmtArgs: config.rustfmtArgs, diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index b72206d3cb..3ade7e900f 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -154,6 +154,7 @@ export class Config { get excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; } get useClientWatching() { return this.cfg.get("useClientWatching") as boolean; } get featureFlags() { return this.cfg.get("featureFlags") as Record; } + get additionalOutDirs() { return this.cfg.get("additionalOutDirs") as Record; } get rustfmtArgs() { return this.cfg.get("rustfmtArgs") as string[]; } get cargoWatchOptions(): CargoWatchOptions {